diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000000..b3bddd20cbc0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +.classpath +!/.project +.project +.settings +target +test-output/ +bin/ +*.ipr +*.iml +*.iws +*.orig +.idea/ +.DS_Store +.idea +overlays/ +pom.xml.versionsBackup +build.xml +*.log +rebel.xml +**/.checkstyle +*.log.gz +.fbIncludeFilterFile diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000000..e8e60a88d8f1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,58 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Steps to enable this build are: +# +# Generate the OAUTH-TOKEN at https://github.com/settings/applications +# Install travis: gem install travis +# Login to travis: travis login +# Encrypt the token: travis encrypt GH_TOKEN=[OAUTH-TOKEN] -r Jasig/cas --add env.global +# + +language: java +sudo: false +branches: + only: + - master +jdk: +- oraclejdk7 +- oraclejdk8 +cache: + directories: + - '$HOME/.m2/repository' +env: + global: + - secure: "JlFTItSTHxmevCoX1fXWkXtQOqP8ERT5ndeYMc378acqZIWEpmoSQelP8unRjtU6sU/4dxdUWpEFLrbcNQsFuPsgzMLGSwuEcjbuSBGMgdAHXSl6+FfBWrCcde2WIfk+eYMm7mrhcySWMvtWss1kDOu+s8+HtRvnRCAsfz+77hs=" + - secure: "iWPPLKSS3zBs2adqLPkMiHfCj2hSLyD5BoV3oodhR7Ne83Kpn1khRcEWFoHF3Ed11eSU+glNdPSzUpc8TzwTZGx5B3RU2Qp36hZFyjuzNWJARmoVPYMiEg3FFBQrUR75w+Tbtn6zPkiAk6nl0K5ewmY0/xixVdnTLXL5HjpE2rc=" + - secure: "f3mDIZ8m6NYJXI8KvWD/sZRSeCCyIyfgPRy3Q6o9u9WyHZuYaJf95Ia0eJQ3gxUDS1TKL31Vk08dhFKrfIcKgifFPa2uQ2uyJkvGxlarMTQ+tpqsZYp4zAJgKc9r4xdZasvF2k4xqr+pl9AFjlpXB4jDD59XPXt3DcRABOYA9sM=" +before_install: +- chmod -R 777 ./travis/init-travis-build.sh +- ./travis/init-travis-build.sh +install: +- mvn -T 10 install -P nocheck -Dmaven.javadoc.skip=true -B -V -q +script: +- mvn -T 10 clean package -Dlog4j.configurationFile=../travis/log4j2.xml +after_success: +- chmod -R 777 ./travis/push-javadoc-to-gh-pages.sh +- ./travis/push-javadoc-to-gh-pages.sh +- chmod -R 777 ./travis/deploy-to-sonatype.sh +- ./travis/deploy-to-sonatype.sh +- mvn clean test cobertura:cobertura coveralls:cobertura -Dlog4j.configuration=file:../travis/log4j2.xml -Dcheckstyle.skip=true -Dlicense.skip=true -Dnotice.skip=true -Dversions.skip=true +#- chmod -R 777 ./travis/test-commit-message.sh +#- ./travis/test-commit-message.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000000..ce0f45b2a9a5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,3 @@ +# Contributor Guidelines + +Please see the [Contributor Guidelines](http://jasig.github.io/cas/developer/Contributor-Guidelines.html) for more info. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 000000000000..35e1816924b6 --- /dev/null +++ b/NOTICE @@ -0,0 +1,242 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + "Java Concurrency in Practice" book annotations under Creative Commons Attribution License + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache Commons Logging under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apache Log4j Web under The Apache Software License, Version 2.0 + Apache Santuario under The Apache Software License, Version 2.0 + Apache Shiro :: Core under The Apache Software License, Version 2.0 + Apache Velocity under The Apache Software License, Version 2.0 + Apereo CAS - Uber WAR - DEPRECATED under Apache 2 + Apereo CAS ClearPass Extension - DEPRECATED under Apache 2 + Apereo CAS Client Protocols Support using pac4j under Apache 2 + Apereo CAS Core under Apache 2 + Apereo CAS Ehcache Integration under Apache 2 + Apereo CAS Generic Support under Apache 2 + Apereo CAS JBoss Cache Integration - DEPRECATED under Apache 2 + Apereo CAS JDBC Support under Apache 2 + Apereo CAS LDAP Support under Apache 2 + Apereo CAS Legacy Support under Apache 2 + Apereo CAS Management Web Application under Apache 2 + Apereo CAS Memcached Integration under Apache 2 + Apereo CAS OAuth Server Support under Apache 2 + Apereo CAS OpenId Server Support under Apache 2 + Apereo CAS RADIUS Support under Apache 2 + Apereo CAS REST Implementation under Apache 2 + Apereo CAS Restlet Integration - DEPRECATED under Apache 2 + Apereo CAS SAML Server and Validation Support under Apache 2 + Apereo CAS SPNEGO/NTLM Support under Apache 2 + Apereo CAS Trusted User Support under Apache 2 + Apereo CAS Web Application under Apache 2 + Apereo CAS Web Application support under Apache 2 + Apereo CAS X.509 Client Certificate Support under Apache 2 + Apereo Central Authentication Service under Apache 2 + ASM Core under BSD + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + avalon-framework under Apache License, Version 2.0 + Bean Validation API under The Apache Software License, Version 2.0 + Bouncy Castle Provider under Bouncy Castle Licence + c3p0:JDBC DataSources/Resource Pools under GNU LESSER GENERAL PUBLIC LICENSE + CAS server security filter under Apache 2 + CDI APIs under Apache License, Version 2.0 + Central Authentication Service under Jasig License + cglib-full under Apache License, Version 1.1 + ClassMate under The Apache Software License, Version 2.0 + Code Generation Library under ASF 2.0 + Collections under The Apache Software License, Version 2.0 + Commons BeanUtils under The Apache Software License, Version 2.0 + Commons Chain under The Apache Software License, Version 2.0 + Commons CLI under The Apache Software License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons Configuration under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Lang under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Commons Pool under The Apache Software License, Version 2.0 + commons-beanutils under Apache License, Version 2.0 + commons-beanutils-core under Apache License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + Cryptacular Library under Apache 2 or GNU Lesser General Public License + Digester under The Apache Software License, Version 2.0 + dom4j under BSD License + ehcache under The Apache Software License, Version 2.0 + Ehcache Core under The Apache Software License, Version 2.0 + Ehcache JCache Implementation under The Apache Software License, Version 2.0 + ehcache-terracotta under Terracotta Public License + ESAPI under BSD or Creative Commons 3.0 BY-SA + ESAPI 2.0 under BSD or Creative Commons 3.0 BY-SA + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + ezmorph under The Apache Software License, Version 2.0 + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + gnu-crypto under GNU General Public License, with the "library exception" + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + guice under The Apache Software License, Version 2.0 + Hamcrest Core under BSD style + Hibernate Commons Annotations under GNU Lesser General Public License + Hibernate JPA Support under GNU Lesser General Public License + Hibernate Validator Engine under Apache License, Version 2.0 + HttpClient under Apache License + HyperSQL Database under HSQLDB License, a BSD open source license + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Inspektr - Error Logging under Apache 2.0 License + Inspektr - Spring Framework Support under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson Integration for Metrics under Apache License 2.0 + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Jasig CAS Client for Java - Core under Apache License Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Commons Development and Distribution License, Version 1.0 + java-getopt under GNU General Public License, with the "library exception" + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Cache - Core Edition under GNU Lesser General Public License + JBoss Common Classes under lgpl + JBoss Logging 3 under Apache License, version 2.0 + JBoss Logging I18n Annotations under Public Domain + JBoss Logging Programming Interface under lgpl + jcifs under GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1 + JCL 1.1.1 implemented over SLF4J under MIT License + jdom under Apache style license + jersey-core under CDDL 1.1 or GPL2 w/ CPE + jersey-server under CDDL 1.1 or GPL2 w/ CPE + jersey-servlet under CDDL 1.1 or GPL2 w/ CPE + jersey-spring under CDDL 1.1 or GPL2 w/ CPE + Jettison under Apache License, Version 2.0 + JGroups under Library (or Lesser) GNU Public License 2.1 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + jradius-core-1.1.3 under GNU Lessor/Library Public License, Version 3.0 or GNU Public License, Version 3.0 + jradius-dictionary-1.1.3 under GNU Lessor/Library Public License, Version 3.0 or GNU Public License, Version 3.0 + json-lib under Apache License, Version 2.0 + JSR 105 - Java(TM) XML Digital Signature API under JDL license + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + jsr311-api under CDDL License + jstl under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + JVM Integration for Metrics under Apache License 2.0 + Kryo under New BSD License + kryo serializers under The Apache Software License, Version 2.0 + Lang under The Apache Software License, Version 2.0 + LDAPTIVE under Apache 2 or GNU Lesser General Public License + LDAPTIVE UNBOUNDID PROVIDER under Apache 2 or GNU Lesser General Public License + logkit under Apache License, Version 2.0 + Metrics Core under Apache License 2.0 + Metrics Health Checks under Apache License 2.0 + Metrics Spring Integration under Apache License 2.0 + Metrics Utility Servlets under Apache License 2.0 + MinLog under New BSD License + Mockito under The MIT License + MXP1: Xml Pull Parser 3rd Edition (XPP3) under Indiana University Extreme! Lab Software License, vesion 1.1.1 or Public Domain + Neko HTML under The Apache Software License, Version 2.0 + Not Yet Commons SSL under Apache License v2 + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + OpenID4Java under Apache 2 + OpenSAML-J under Apache 2 + OpenWS under Apache 2 + org.samba.jcifs:jcifs-ext under GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1 + pac4j core under The Apache Software License, Version 2.0 + pac4j for HTTP protocol under The Apache Software License, Version 2.0 + pac4j for OAuth protocol under The Apache Software License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + quartz under The Apache Software License, Version 2.0 + ReflectASM under New BSD License + Reflections under WTFPL or The New BSD License + Restlet Core - API and Engine under Apache 2.0 license or LGPL 3.0 license or LGPL 2.1 license or CDDL 1.0 license or EPL 1.0 license + Restlet Extension - Servlet under Apache 2.0 license or LGPL 3.0 license or LGPL 2.1 license or CDDL 1.0 license or EPL 1.0 license + Restlet Extension - SLF4J under Apache 2.0 license or LGPL 3.0 license or LGPL 2.1 license or CDDL 1.0 license or EPL 1.0 license + Restlet Extension - Spring Framework under Apache 2.0 license or LGPL 3.0 license or LGPL 2.1 license or CDDL 1.0 license or EPL 1.0 license + Scribe OAuth Library under MIT + serializer under Apache License, Version 2.0 + servlet-api under Commons Development and Distribution License, Version 1.0 + SLF4J API Module under MIT License + SLF4J LOG4J-12 Binding under MIT License + SOJO under The Apache Software License, Version 2.0 + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Json View under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + Spring Webflow Client Repository under Apache 2 + spring-security-cas under The Apache Software License, Version 2.0 + spring-security-config under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + spring-security-web under The Apache Software License, Version 2.0 + Spymemcached under The Apache Software License, Version 2.0 + standard under Apache License, Version 2.0 + StAX API under The Apache Software License, Version 2.0 + Stax2 API under The BSD License + Streaming API for XML under Sun Binary Code License + terracotta-toolkit-runtime under Terracotta Public License + UnboundID LDAP SDK for Java under GNU General Public License version 2 (GPLv2) or GNU Lesser General Public License version 2.1 (LGPLv2.1) or UnboundID LDAP SDK Free Use License + VT Crypt Library under Apache 2 or GNU Lesser General Public License + Woodstox under The Apache Software License, Version 2.0 + Xalan Java under The Apache Software License, Version 2.0 + Xalan Java Serializer under The Apache Software License, Version 2.0 + Xerces2 Java Parser under The Apache Software License, Version 2.0 + Xerces2-j under The Apache Software License, Version 2.0 + xercesImpl under Apache License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + XML Commons Resolver Component under The Apache Software License, Version 2.0 + XML Security under The Apache Software License, Version 2.0 + xml-apis under Apache License, Version 2.0 + XMLTooling-J under Apache 2 + XStream Core under BSD style + diff --git a/README.md b/README.md new file mode 100644 index 000000000000..050c4e3e6179 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# Central Authentication Service (CAS) + + + +## Introduction + +Welcome to the home of the Central Authentication Service project, more commonly referred to as CAS. The Central Authentication Service (CAS) is the standard mechanism by which web applications should authenticate users. + +CAS provides enterprise single sign-on service: + +- An open and well-documented protocol +- An open-source Java server component +- A library of clients for Java, .Net, PHP, Perl, Apache, uPortal, and others +- Integrates with uPortal, BlueSocket, TikiWiki, Mule, Liferay, Moodle and others +- Community documentation and implementation support +- An extensive community of adopters + +## Build [![Build Status](https://api.travis-ci.org/Jasig/cas.png)](http://travis-ci.org/Jasig/cas) [![Codeship Status for Jasig/cas](https://www.codeship.io/projects/a204a3a0-727c-0131-ab14-4e46b2fa20d2/status)](https://www.codeship.io/projects/13661) [![Coverage Status](https://coveralls.io/repos/Jasig/cas/badge.png?branch=master)](https://coveralls.io/r/Jasig/cas?branch=master) [![Dependency Management](https://www.versioneye.com/user/projects/54ac6d74b6c7ff65b8000072/badge.svg?style=flat)](https://www.versioneye.com/user/projects/54ac6d74b6c7ff65b8000072) [![Issue Stats](http://www.issuestats.com/github/Jasig/cas/badge/pr?style=flat)](http://www.issuestats.com/github/Jasig/cas) [![Issue Stats](http://www.issuestats.com/github/Jasig/cas/badge/issue?style=flat)](http://www.issuestats.com/github/Jasig/cas) + + + +It is recommended to build and deploy CAS locally using the [Maven War Overlay method][overlay]. +This approach does not require the adopter to *explicitly* download any version of CAS, but +rather utilizes Maven's overlay mechanism to combine CAS original artifacts and local +customizations to further ease future upgrades and maintenance. + +## Download [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.jasig.cas/cas-server/badge.svg?style=flat)](https://maven-badges.herokuapp.com/maven-central/org.jasig.cas/cas-server) [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/Jasig/cas/trend.png)](https://bitdeli.com/free "Bitdeli Badge") +- Binary releases may be downloaded from [here][downloadcas]. +- CAS artifacts are published through the [Maven Central Repository][casmavencentral]. +- A snapshot of the codebase's `master` branch may be downloaded from [here][downloadcasgithub]. +- Snapshot artifacts are also published through the [Sonatype snapashots repository][cassonatype] under the group id **`org.jasig.cas`**. +- The codebase may also be *cloned* using a Git client via the following command: +```bash +git clone git@github.com:Jasig/cas.git +``` + +**Note:** If building CAS from the source, running the test cases currently requires an active Internet connection. +Please [see the maven docs][skip] on how to disable the tests. + +## Documentation [![License](https://img.shields.io/hexpm/l/plug.svg)](https://github.com/Jasig/cas/blob/master/LICENSE) +- [Official Documentation][wiki] +- [Release Notes][releasenotes] +- [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/Jasig/cas?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +## Addons +- [CAS Addons][casaddons] is an open source collection of useful CAS server extensions. + +## Contributions +- [How to contribute][contribute] + +[wiki]: http://jasig.github.io/cas +[overlay]: http://jasig.github.io/cas/development/installation/Maven-Overlay-Installation.html +[skip]: http://maven.apache.org/general.html#skip-test +[contribute]: http://jasig.github.io/cas/developer/Contributor-Guidelines.html +[downloadcas]: http://www.apereo.org/cas/download +[cassonatype]: https://oss.sonatype.org/content/repositories/snapshots/org/jasig/cas/ +[casmavencentral]: http://mvnrepository.com/artifact/org.jasig.cas +[downloadcasgithub]: https://github.com/Jasig/cas/archive/master.zip +[releasenotes]: https://github.com/Jasig/cas/releases +[casaddons]: https://github.com/unicon-cas-addons diff --git a/assembly.xml b/assembly.xml new file mode 100644 index 000000000000..77965e733a0c --- /dev/null +++ b/assembly.xml @@ -0,0 +1,139 @@ + + + + release + + zip + tar.gz + + true + + + unix + true + ${basedir} + + + *.xml + *.txt + + + + + + + + cas-server-webapp + + + + + src + src + unix + true + + **/*.java + **/*.xml + **/*.properties + **/*.jsp + **/*.css + **/*.jsp + **/*.html + **/*.txt + **/*.js + **/*.conf + **/*.crt + **/*.crl + + + + + src + src + + **/*.gif + **/*.ico + **/*.key + + + + + unix + true + + *.xml + + + + + unix + target/site/apidocs/ + true + docs + + **/* + + + + + true + true + + + modules + false + false + + + + + + + cas-server-webapp + + + + + unix + true + + *.xml + + + + true + true + + + modules + false + false + cas.war + + + + + diff --git a/cas-management-webapp/NOTICE b/cas-management-webapp/NOTICE new file mode 100644 index 000000000000..9b005028ff06 --- /dev/null +++ b/cas-management-webapp/NOTICE @@ -0,0 +1,158 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + "Java Concurrency in Practice" book annotations under Creative Commons Attribution License + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apache Log4j Web under The Apache Software License, Version 2.0 + Apache Santuario under The Apache Software License, Version 2.0 + Apache Velocity under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS Generic Support under Apache 2 + Apereo CAS Management Web Application under Apache 2 + Apereo CAS Web Application support under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + avalon-framework under Apache License, Version 2.0 + Bean Validation API under The Apache Software License, Version 2.0 + Bouncy Castle Provider under Bouncy Castle Licence + c3p0:JDBC DataSources/Resource Pools under GNU LESSER GENERAL PUBLIC LICENSE + CDI APIs under Apache License, Version 2.0 + cglib-full under Apache License, Version 1.1 + ClassMate under The Apache Software License, Version 2.0 + Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + commons-beanutils under Apache License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + Cryptacular Library under Apache 2 or GNU Lesser General Public License + dom4j under BSD License + ESAPI 2.0 under BSD or Creative Commons 3.0 BY-SA + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + ezmorph under The Apache Software License, Version 2.0 + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Hibernate Validator Engine under Apache License, Version 2.0 + HttpClient under Apache License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Inspektr - Error Logging under Apache 2.0 License + Inspektr - Spring Framework Support under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson Integration for Metrics under Apache License 2.0 + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Jasig CAS Client for Java - Core under Apache License Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + JCL 1.1.1 implemented over SLF4J under MIT License + jersey-core under CDDL 1.1 or GPL2 w/ CPE + jersey-server under CDDL 1.1 or GPL2 w/ CPE + jersey-servlet under CDDL 1.1 or GPL2 w/ CPE + jersey-spring under CDDL 1.1 or GPL2 w/ CPE + Jettison under Apache License, Version 2.0 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + json-lib under Apache License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + jsr311-api under CDDL License + jstl under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + JVM Integration for Metrics under Apache License 2.0 + Lang under The Apache Software License, Version 2.0 + logkit under Apache License, Version 2.0 + Metrics Core under Apache License 2.0 + Metrics Health Checks under Apache License 2.0 + Metrics Utility Servlets under Apache License 2.0 + Mockito under The MIT License + MXP1: Xml Pull Parser 3rd Edition (XPP3) under Indiana University Extreme! Lab Software License, vesion 1.1.1 or Public Domain + Not Yet Commons SSL under Apache License v2 + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + OpenSAML-J under Apache 2 + OpenWS under Apache 2 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + quartz under The Apache Software License, Version 2.0 + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + SOJO under The Apache Software License, Version 2.0 + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Json View under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + Spring Webflow Client Repository under Apache 2 + spring-security-cas under The Apache Software License, Version 2.0 + spring-security-config under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + spring-security-web under The Apache Software License, Version 2.0 + standard under Apache License, Version 2.0 + StAX API under The Apache Software License, Version 2.0 + Stax2 API under The BSD License + Streaming API for XML under Sun Binary Code License + Woodstox under The Apache Software License, Version 2.0 + Xalan Java under The Apache Software License, Version 2.0 + Xalan Java Serializer under The Apache Software License, Version 2.0 + Xerces2-j under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + XML Commons Resolver Component under The Apache Software License, Version 2.0 + XMLTooling-J under Apache 2 + XStream Core under BSD style + diff --git a/cas-management-webapp/pom.xml b/cas-management-webapp/pom.xml new file mode 100644 index 000000000000..6a14f7880d00 --- /dev/null +++ b/cas-management-webapp/pom.xml @@ -0,0 +1,203 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-management-webapp + war + Apereo CAS Management Web Application + + + + org.jasig.inspektr + inspektr-support-spring + runtime + + + + org.springframework.security + spring-security-core + compile + + + + org.springframework.security + spring-security-web + compile + + + + org.springframework.security + spring-security-cas + runtime + + + + org.springframework.security + spring-security-config + runtime + + + + org.springframework + spring-aop + + + + org.jasig.cas + cas-server-core + ${project.version} + + + org.springframework.webflow + spring-webflow + + + org.opensaml + opensaml + + + + + + org.jasig.cas + cas-server-webapp-support + ${project.version} + runtime + + + + org.jasig.cas.client + cas-client-core + + + + org.springframework + spring-context-support + compile + + + + org.springframework + spring-expression + compile + + + + net.sf.spring-json + spring-json + 1.3.1 + compile + + + net.sf.sojo + sojo-optional + + + org.springframework + spring + + + org.springframework + spring-mock + + + org.springframework + spring-webmvc + + + servlet-api + javax.servlet + + + log4j + log4j + + + + + + net.sf.sojo + sojo + 1.0.5 + + + commons-attributes + commons-attributes-api + + + commons-logging + commons-logging + + + + + + javax.servlet + jstl + 1.1.2 + jar + + + + taglibs + standard + 1.1.2 + jar + + + + org.hibernate + hibernate-validator + runtime + + + + + + + org.apache.maven.plugins + maven-war-plugin + + cas-management + + + ${basedir}/src/main/webapp/WEB-INF + true + WEB-INF + + **/web.xml + + + + + + + + + + ${project.parent.basedir} + + diff --git a/cas-management-webapp/src/main/java/org/jasig/cas/services/web/ManageRegisteredServicesMultiActionController.java b/cas-management-webapp/src/main/java/org/jasig/cas/services/web/ManageRegisteredServicesMultiActionController.java new file mode 100644 index 000000000000..9f5686c7d7c3 --- /dev/null +++ b/cas-management-webapp/src/main/java/org/jasig/cas/services/web/ManageRegisteredServicesMultiActionController.java @@ -0,0 +1,151 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services.web; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.validation.constraints.NotNull; + +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.view.RedirectView; + +/** + * MultiActionController to handle the deletion of RegisteredServices as well as + * displaying them on the Manage Services page. + * + * @author Scott Battaglia + * @since 3.1 + */ +@Controller +public final class ManageRegisteredServicesMultiActionController { + + /** Instance of ServicesManager. */ + @NotNull + private final ServicesManager servicesManager; + + @NotNull + private final String defaultServiceUrl; + + /** + * Instantiates a new manage registered services multi action controller. + * + * @param servicesManager the services manager + * @param defaultServiceUrl the default service url + */ + @Autowired + public ManageRegisteredServicesMultiActionController(final ServicesManager servicesManager, + @Value("${cas-management.securityContext.serviceProperties.service}") final String defaultServiceUrl) { + this.servicesManager = servicesManager; + this.defaultServiceUrl = defaultServiceUrl; + } + + /** + * Authorization failure handling. Simply returns the view name. + * + * @return the view name. + */ + @RequestMapping(value="authorizationFailure.html", method={RequestMethod.GET}) + public String authorizationFailureView() { + return "authorizationFailure"; + } + + /** + * Logout handling. Simply returns the view name. + * + * @return the view name. + */ + @RequestMapping(value="loggedOut.html", method={RequestMethod.GET}) + public String logoutView() { + return "logout"; + } + + + /** + * Method to delete the RegisteredService by its ID. + * + * @param idAsLong the id + * @return the Model and View to go to after the service is deleted. + */ + @RequestMapping(value="deleteRegisteredService.html", method={RequestMethod.GET}) + public ModelAndView deleteRegisteredService( + @RequestParam("id") final long idAsLong) { + + final ModelAndView modelAndView = new ModelAndView(new RedirectView( + "manage.html", true), "status", "deleted"); + + final RegisteredService r = this.servicesManager.delete(idAsLong); + modelAndView.addObject("serviceName", r != null ? r.getName() : ""); + + return modelAndView; + } + + /** + * Method to show the RegisteredServices. + * @return the Model and View to go to after the services are loaded. + */ + @RequestMapping(value="manage.html", method={RequestMethod.GET}) + public ModelAndView manage() { + final Map model = new HashMap<>(); + + final List services = new ArrayList<>(this.servicesManager.getAllServices()); + + model.put("services", services); + model.put("pageTitle", "Manage"); + model.put("defaultServiceUrl", this.defaultServiceUrl); + + return new ModelAndView("manage", model); + } + + /** + * Updates the {@link RegisteredService#getEvaluationOrder()}. Expects an id parameter to indicate + * the {@link RegisteredService#getId()} and the new evaluationOrder integer parameter from the request. + * as parameters. + * + * @param id the id + * @param evaluationOrder the evaluation order + * @return {@link ModelAndView} object that redirects to a jsonView. The model will contain a + * a parameter error whose value should describe the error occurred if the update is unsuccessful. + * There will also be a successful boolean parameter that indicates whether or not the update + * was successful. + */ + @RequestMapping(value="updateRegisteredServiceEvaluationOrder.html", method={RequestMethod.GET}) + public ModelAndView updateRegisteredServiceEvaluationOrder(@RequestParam("id") final long id, + @RequestParam("evaluationOrder") final int evaluationOrder) { + final RegisteredService svc = this.servicesManager.findServiceBy(id); + if (svc == null) { + throw new IllegalArgumentException("Service id " + id + " cannot be found."); + } + + svc.setEvaluationOrder(evaluationOrder); + this.servicesManager.save(svc); + + return new ModelAndView("jsonView"); + } +} diff --git a/cas-management-webapp/src/main/java/org/jasig/cas/services/web/RegisteredServiceSimpleFormController.java b/cas-management-webapp/src/main/java/org/jasig/cas/services/web/RegisteredServiceSimpleFormController.java new file mode 100644 index 000000000000..b1b95457b638 --- /dev/null +++ b/cas-management-webapp/src/main/java/org/jasig/cas/services/web/RegisteredServiceSimpleFormController.java @@ -0,0 +1,236 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services.web; + +import org.jasig.cas.services.RegexRegisteredService; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.RegisteredServiceImpl; +import org.jasig.cas.services.ServicesManager; +import org.jasig.services.persondir.IPersonAttributeDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.propertyeditors.StringTrimmerEditor; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.util.StringUtils; +import org.springframework.validation.BindingResult; +import org.springframework.validation.Validator; +import org.springframework.web.bind.ServletRequestDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.view.RedirectView; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * SimpleFormController to handle adding/editing of RegisteredServices. + * + * @author Scott Battaglia + * @since 3.1 + */ +@Controller +public final class RegisteredServiceSimpleFormController { + + private static final Logger LOGGER = LoggerFactory.getLogger(RegisteredServiceSimpleFormController.class); + + private static final String COMMAND_NAME = "registeredService"; + + /** Instance of ServiceRegistryManager. */ + @NotNull + private final ServicesManager servicesManager; + + /** Instance of AttributeRegistry. */ + @NotNull + private final IPersonAttributeDao personAttributeDao; + + @NotNull + @Resource(name="registeredServiceValidator") + private Validator validator; + + /** + * Instantiates a new registered service simple form controller. + * + * @param servicesManager the services manager + * @param personAttributeDao the attribute repository + */ + @Autowired + public RegisteredServiceSimpleFormController(final ServicesManager servicesManager, + final IPersonAttributeDao personAttributeDao) { + this.personAttributeDao = personAttributeDao; + this.servicesManager = servicesManager; + } + + /** + * Instantiates a new registered service simple form controller. + * + * @param servicesManager the services manager + * @param personAttributeDao the person attribute dao + * @param validator the validator + */ + RegisteredServiceSimpleFormController(final ServicesManager servicesManager, + final IPersonAttributeDao personAttributeDao, final Validator validator) { + this(servicesManager, personAttributeDao); + this.validator = validator; + } + + /** + * Sets the require fields and the disallowed fields from the + * HttpServletRequest. + * + * @param request the request + * @param binder the binder + * @throws Exception the exception + */ + @InitBinder + protected void initBinder(final HttpServletRequest request, final ServletRequestDataBinder binder) throws Exception { + binder.setRequiredFields("description", "serviceId", + "name", "enabled", "ssoEnabled", + "anonymousAccess", "evaluationOrder"); + binder.setDisallowedFields("id"); + binder.registerCustomEditor(String.class, new StringTrimmerEditor(true)); + } + + /** + * Adds the service to the ServiceRegistry via the ServiceRegistryManager. + * + * @param service the service + * @param result the binding result + * @param model the page model + * @param request the http request + * @return the model and view + * @throws Exception the exception + */ + @RequestMapping(method = RequestMethod.POST) + protected ModelAndView onSubmit(@ModelAttribute(COMMAND_NAME) final RegisteredService service, + final BindingResult result, final ModelMap model, final HttpServletRequest request) throws Exception { + + updateModelMap(model, request); + + this.validator.validate(service, result); + if (result.hasErrors()) { + model.addAttribute("validationErrors", result.getAllErrors()); + return render(request, model); + } + + RegisteredService svcToUse = service; + if (service.getServiceId().startsWith("^") && service instanceof RegisteredServiceImpl) { + LOGGER.debug("Detected regular expression starting with ^"); + final RegexRegisteredService regexService = new RegexRegisteredService(); + regexService.copyFrom(service); + svcToUse = regexService; + } else if (!service.getServiceId().startsWith("^") && service instanceof RegexRegisteredService) { + LOGGER.debug("Detected ant expression {}", service.getServiceId()); + final RegisteredServiceImpl regexService = new RegisteredServiceImpl(); + regexService.copyFrom(service); + svcToUse = regexService; + } + + this.servicesManager.save(svcToUse); + LOGGER.info("Saved changes to service {}", svcToUse.getId()); + + final ModelAndView modelAndView = new ModelAndView(new RedirectView( + "manage.html#" + svcToUse.getId(), true)); + modelAndView.addObject("action", "add"); + modelAndView.addObject("id", svcToUse.getId()); + + + + return modelAndView; + } + + /** + * Handles displaying the add and edit view by first updating the view by calling + * {@link #updateModelMap(ModelMap, HttpServletRequest)}. + * @param request the request + * @param model the model + * @return the model and view + * @throws Exception the exception + */ + @RequestMapping(method=RequestMethod.GET, value= {"add.html", "edit.html"}) + protected ModelAndView render(final HttpServletRequest request, final ModelMap model) + throws Exception { + updateModelMap(model, request); + return new ModelAndView("add"); + } + + /** + * Updates model map. The following objects will be available in the model: + * + *
    + *
  • availableAttributes
  • + *
  • availableUsernameAttributes
  • + *
  • pageTitle
  • + *
+ * + * @param model the model + * @param request the request + */ + private void updateModelMap(final ModelMap model, final HttpServletRequest request) { + final List possibleAttributeNames = new ArrayList<>(); + possibleAttributeNames.addAll(this.personAttributeDao.getPossibleUserAttributeNames()); + Collections.sort(possibleAttributeNames); + model.addAttribute("availableAttributes", possibleAttributeNames); + + final List possibleUsernameAttributeNames = new ArrayList<>(); + possibleUsernameAttributeNames.addAll(possibleAttributeNames); + possibleUsernameAttributeNames.add(0, ""); + model.addAttribute("availableUsernameAttributes", possibleUsernameAttributeNames); + + final String path = request.getServletPath().replace("/", "").replace(".html", "").concat("ServiceView"); + model.addAttribute("pageTitle", path); + } + + /** + * Determines the registered service to be used. + * If no id is specified, a service of type + * {@link RegisteredServiceImpl} will be created by default. + * + * @param id the id + * @return the service + */ + @ModelAttribute(COMMAND_NAME) + public RegisteredService getCommand(@RequestParam(value="id", required=false) final String id) { + + if (!StringUtils.hasText(id)) { + final RegisteredService service = new RegisteredServiceImpl(); + LOGGER.debug("Created new service of type {}", service.getClass().getName()); + return service; + } + + final RegisteredService service = this.servicesManager.findServiceBy(Long.parseLong(id)); + + if (service != null) { + LOGGER.debug("Loaded service {}", service.getServiceId()); + } else { + LOGGER.debug("Invalid service id specified."); + } + + return service; + } +} diff --git a/cas-management-webapp/src/main/java/org/jasig/cas/services/web/package.html b/cas-management-webapp/src/main/java/org/jasig/cas/services/web/package.html new file mode 100644 index 000000000000..caf4cfd6e8d2 --- /dev/null +++ b/cas-management-webapp/src/main/java/org/jasig/cas/services/web/package.html @@ -0,0 +1,25 @@ + + + +

This package contains all the controllers and validator for the services management.

+ + diff --git a/cas-management-webapp/src/main/java/org/jasig/cas/services/web/support/RegisteredServiceValidator.java b/cas-management-webapp/src/main/java/org/jasig/cas/services/web/support/RegisteredServiceValidator.java new file mode 100644 index 000000000000..99b7655080e2 --- /dev/null +++ b/cas-management-webapp/src/main/java/org/jasig/cas/services/web/support/RegisteredServiceValidator.java @@ -0,0 +1,112 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services.web.support; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; + +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.services.persondir.IPersonAttributeDao; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; + +/** + * RegisteredServiceValidator ensures that a new RegisteredService does not have + * a conflicting Service Id with another service already in the registry. + * + * @author Scott Battaglia + * @since 3.1 + */ +@Component +@Qualifier("registeredServiceValidator") +public final class RegisteredServiceValidator implements Validator { + + /** Default length, which matches what is in the view. */ + private static final int DEFAULT_MAX_DESCRIPTION_LENGTH = 300; + + /** + * {@link ServicesManager} to look up services. + **/ + @NotNull + private final ServicesManager servicesManager; + + /** The maximum length of the description we will accept. */ + @Min(0) + private int maxDescriptionLength = DEFAULT_MAX_DESCRIPTION_LENGTH; + + /** + * {@link IPersonAttributeDao} to manage person attributes. + **/ + @NotNull + private final IPersonAttributeDao personAttributeDao; + + /** + * Instantiates a new registered service validator. + * + * @param servicesManager the services manager + * @param personAttributeDao the person attribute dao + */ + @Autowired + public RegisteredServiceValidator(final ServicesManager servicesManager, + final IPersonAttributeDao personAttributeDao) { + this.personAttributeDao = personAttributeDao; + this.servicesManager = servicesManager; + } + /** + * {@inheritDoc} + * Supports {@link RegisteredService} objects. + * + * @see org.springframework.validation.Validator#supports(java.lang.Class) + */ + @Override + public boolean supports(final Class clazz) { + return RegisteredService.class.isAssignableFrom(clazz); + } + + @Override + public void validate(final Object o, final Errors errors) { + final RegisteredService r = (RegisteredService) o; + + if (r.getServiceId() != null) { + for (final RegisteredService service : this.servicesManager.getAllServices()) { + if (r.getServiceId().equals(service.getServiceId()) + && r.getId() != service.getId()) { + errors.rejectValue("serviceId", + "registeredService.serviceId.exists", null); + break; + } + } + } + + if (r.getDescription() != null + && r.getDescription().length() > this.maxDescriptionLength) { + errors.rejectValue("description", + "registeredService.description.length", null); + } + } + + public void setMaxDescriptionLength(final int maxLength) { + this.maxDescriptionLength = maxLength; + } + +} diff --git a/cas-management-webapp/src/main/java/org/jasig/cas/web/view/AjaxAwareJsonExceptionResolver.java b/cas-management-webapp/src/main/java/org/jasig/cas/web/view/AjaxAwareJsonExceptionResolver.java new file mode 100644 index 000000000000..d6072ba94afe --- /dev/null +++ b/cas-management-webapp/src/main/java/org/jasig/cas/web/view/AjaxAwareJsonExceptionResolver.java @@ -0,0 +1,78 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.view; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.view.json.exception.JsonExceptionResolver; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Implementation of JsonExceptionResolver that only triages the exception occurred + * for JSON requests. + * + * @author Misagh Moayyed + * @since 4.0.0 + */ +public class AjaxAwareJsonExceptionResolver extends JsonExceptionResolver { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private String ajaxRequestHeaderName ="x-requested-with"; + private String ajaxRequestHeaderValue = "XMLHttpRequest"; + + /** + * Header name that identifies this request as ajax. + * @param ajaxRequestHeaderName header name + */ + public final void setAjaxRequestHeaderName(final String ajaxRequestHeaderName) { + this.ajaxRequestHeaderName = ajaxRequestHeaderName; + } + + /** + * Header value that identifies this request as ajax. + * @param ajaxRequestHeaderValue header value + */ + public final void setAjaxRequestHeaderValue(final String ajaxRequestHeaderValue) { + this.ajaxRequestHeaderValue = ajaxRequestHeaderValue; + } + + /** + * {@inheritDoc} + */ + @Override + public ModelAndView resolveException(final HttpServletRequest request, final HttpServletResponse response, + final Object handler, final Exception ex) { + final String contentType = request.getHeader(this.ajaxRequestHeaderName); + if (contentType != null && contentType.equals(this.ajaxRequestHeaderValue)) { + logger.debug("Handling exception {} for ajax request indicated by header {}", + ex.getClass().getName(), this.ajaxRequestHeaderName); + return super.resolveException(request, response, handler, ex); + } else { + logger.trace("Unable to resolve exception {} for request. Ajax request header {} not found.", + ex.getClass().getName(), this.ajaxRequestHeaderName); + } + logger.debug(ex.getMessage(), ex); + return null; + } + +} diff --git a/cas-management-webapp/src/main/resources/default_views.properties b/cas-management-webapp/src/main/resources/default_views.properties new file mode 100644 index 000000000000..5a8e730f7364 --- /dev/null +++ b/cas-management-webapp/src/main/resources/default_views.properties @@ -0,0 +1,23 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# A placeholder for view definitions that are to be defined +# in the format of: +# viewName.(class)=org.jasig.cas.web.view.ViewClassName +# This file is exclusively reserved for custom views diff --git a/cas-management-webapp/src/main/resources/log4j2.xml b/cas-management-webapp/src/main/resources/log4j2.xml new file mode 100644 index 000000000000..2a745a738318 --- /dev/null +++ b/cas-management-webapp/src/main/resources/log4j2.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-management-webapp/src/main/resources/messages.properties b/cas-management-webapp/src/main/resources/messages.properties new file mode 100644 index 000000000000..3dbddf2f4f18 --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages.properties @@ -0,0 +1,90 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Blocked Errors Page +screen.blocked.header=Access Denied +screen.blocked.message=You are not authorized to access this resource. Contact your CAS administrator for more info. + +#Logout Screen Messages +screen.logout.header=Logout successful +screen.logout.success=You have successfully logged out of the Management CAS application + +# SERVICES MANAGEMENT +addServiceView=Add New Service +editServiceView=Edit Service +manageServiceView=Manage Services + +registeredService.serviceId.exists=A Service with that Service URL already exists. +registeredService.usernameAttribute.notAvailable=Selected username attribute is not available for release. +registeredService.description.length=The description exceeds the maximum number of characters allowed. + +application.title=Apereo Central Authentication Service +application.errors.global=Please correct the errors below: + +management.services.title=Services Management +management.services.link.logout=Log Out + +management.services.status.notdeleted=The service can not be deleted. +management.services.status.deleted={0} has been successfully deleted. +management.services.status.evaluationOrder.notupdated=The service evaluation order can not be updated. + +management.services.add.instructions=Please make sure to commit your changes by clicking on the Save Changes button at the bottom of the page +management.services.add.property.name=Name +management.services.add.property.serviceUrl=Service URL +management.services.add.property.serviceUrl.instructions=You can use Ant-style Pattern Matching +management.services.add.property.description=Description +management.services.add.property.themeName=Theme Name +management.services.add.property.status=Status +management.services.add.property.status.enabled=Enabled + +management.services.add.property.status.ssoParticipant=SSO Participant +management.services.add.property.status.anonymousAccess=Anonymous Access +management.services.add.property.attributes=Attributes +management.services.add.property.evaluationOrder=Order + +management.services.add.button.save=Save Changes +management.services.add.button.cancel=Cancel + +management.services.manage.label.name=Service Name +management.services.manage.label.serviceUrl= Service URL +management.services.manage.label.enabled=Enabled + +management.services.manage.label.ssoParticipant=SSO +management.services.manage.label.usernameAttribute=Username +management.services.manage.label.anonymous=Anonymous +management.services.manage.label.attributes=Attributes +management.services.manage.label.evaluationOrder=Order + +management.services.manage.action.edit=edit +management.services.manage.action.delete=delete + +management.services.service.warn=CAS does not allow any applications to use SSO because no services are configured. Any application that wishes to use CAS must be registered in this tool. That includes THIS TOOL. If you are going to use this tool, the FIRST SERVICE TO ADD IS THIS SERVICE ITSELF. The default Service Management Tool URL is "{0}". + +screen.unavailable.heading=The CAS management webapp is Unavailable +screen.unavailable.message=There was an error trying to complete your request. Please notify your support desk or try again. + +footer.links=Links to CAS Resources: +footer.homePage=Home Page +footer.wiki=Wiki +footer.issueTracker=Issue Tracker +footer.mailingLists=Mailing Lists +footer.copyright=Copyright © 2005 - 2015 Apereo, Inc. All rights reserved. +footer.poweredBy=Powered by Apereo Central Authentication Service {0} diff --git a/cas-management-webapp/src/main/resources/messages_ar.properties b/cas-management-webapp/src/main/resources/messages_ar.properties new file mode 100644 index 000000000000..067be5b0f9f5 --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_ar.properties @@ -0,0 +1,88 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Blocked Errors Page +screen.blocked.header=\u0627\u0644\u0648\u0635\u0648\u0644 \u0645\u0631\u0641\u0648\u0636 + +#Logout Screen Messages +screen.logout.header=\u062E\u0631\u0648\u062C \u0646\u0627\u062C\u062D +screen.logout.success=\u0644\u0642\u062F \u0642\u0645\u062A \u0628\u062A\u0633\u062C\u064A\u0644 \u0627\u0644\u062E\u0631\u0648\u062C \u0628\u0646\u062C\u0627\u062D \u0644\u0644\u062E\u0631\u0648\u062C \u0645\u0646 \u0645\u0631\u0643\u0632 \u0627\u0644\u0645\u0635\u0627\u062F\u0642\u0629 + +# SERVICES MANAGEMENT +addServiceView=\u0625\u0636\u0627\u0641\u0629 \u062E\u062F\u0645\u0629 \u062C\u062F\u064A\u062F\u0629 +editServiceView=\u062A\u062D\u0631\u064A\u0631 \u0627\u0644\u062E\u062F\u0645\u0627\u062A +manageServiceView=\u0625\u062F\u0627\u0631\u0629 \u0627\u0644\u062E\u062F\u0645\u0627\u062A + +registeredService.serviceId.exists=\u0627\u0644\u062E\u062F\u0645\u0629 \u0645\u0639 \u062E\u062F\u0645\u0629 \u0647\u0630\u0627 \u0627\u0644\u0639\u0646\u0648\u0627\u0646 \u0645\u0648\u062C\u0648\u062F \u0628\u0627\u0644\u0641\u0639\u0644. + +application.title=Apereo Central Authentication Service +application.errors.global=\u064A\u0631\u062C\u0649 \u062A\u0635\u062D\u064A\u062D \u0627\u0644\u0623\u062E\u0637\u0627\u0621 \u0623\u062F\u0646\u0627\u0647 : + +management.services.title=\u0627\u0644\u062E\u062F\u0645\u0627\u062A \u0627\u0644\u0625\u062F\u0627\u0631\u064A\u0629 +management.services.link.logout=\u0627\u0644\u062E\u0631\u0648\u062C + +management.services.status.notdeleted=\u0644\u0627 \u064A\u0645\u0643\u0646 \u062D\u0630\u0641 \u0627\u0644\u062E\u062F\u0645\u0629 +management.services.status.deleted={0} \u062A\u0645 \u0627\u0644\u062D\u0630\u0641 \u0628\u0646\u062C\u0627\u062D. +management.services.status.evaluationOrder.notupdated=\u0644\u0627 \u064A\u0645\u0643\u0646 \u062A\u062D\u062F\u064A\u062B \u062A\u0631\u062A\u064A\u0628 \u0627\u0644\u062A\u0642\u064A\u064A\u0645 + +management.services.add.instructions=\u0627\u0644\u0631\u062C\u0627\u0621 \u0627\u0644\u062A\u0623\u0643\u062F \u0645\u0646 \u062A\u0646\u0641\u064A\u0630 \u0627\u0644\u062A\u063A\u064A\u064A\u0631\u0627\u062A \u0628\u0627\u0644\u0636\u063A\u0637 \u0639\u0644\u0649 \u0632\u0631 \u062D\u0641\u0638 \u0627\u0644\u062A\u063A\u064A\u064A\u0631\u0627\u062A \u0641\u064A \u0623\u0633\u0641\u0644 \u0627\u0644\u0635\u0641\u062D\u0629 +management.services.add.property.name=\u0627\u0633\u0645 +management.services.add.property.serviceUrl=\u062E\u062F\u0645\u0629 URL +management.services.add.property.serviceUrl.instructions=\u064A\u0645\u0643\u0646\u0643 \u0627\u0633\u062A\u062E\u062F\u0627\u0645 Ant \u0639\u0644\u0649 \u063A\u0631\u0627\u0631 \u0646\u0645\u0637 \u0627\u0644\u0645\u0637\u0627\u0628\u0642\u0629 +management.services.add.property.description=\u0648\u0635\u0641 +management.services.add.property.themeName=\u0645\u0648\u0636\u0648\u0639 \u0627\u0633\u0645 +management.services.add.property.status=\u062D\u0627\u0644\u0629 +management.services.add.property.status.enabled=\u062A\u0645\u06A9\u06CC\u0646 + +management.services.add.property.status.ssoParticipant=SSO \u0645\u0634\u0627\u0631\u06A9 +management.services.add.property.status.anonymousAccess=\u0627\u0644\u0648\u0635\u0648\u0644 \u0627\u0644\u0645\u062C\u0647\u0648\u0644 +management.services.add.property.attributes=\u0633\u0645\u0627\u062A +management.services.add.property.ignoreAttributes=\u062A\u062C\u0627\u0647\u0644 \u0627\u0644\u0625\u062F\u0627\u0631\u0629 \u0639\u0628\u0631 \u0647\u0630\u0627 \u0633\u0645\u0629 \u0623\u062F\u0627\u0629 +management.services.add.property.evaluationOrder=\u0627\u0644\u0646\u0638\u0627\u0645 + +management.services.add.button.save=\u062D\u0641\u0638 \u0627\u0644\u062A\u063A\u064A\u064A\u0631\u0627\u062A +management.services.add.button.cancel=\u0625\u0644\u063A\u0627\u0621 + +management.services.manage.label.name=\u0627\u0633\u0645 \u0627\u0644\u062E\u062F\u0645\u0629 +management.services.manage.label.serviceUrl=\u062E\u062F\u0645\u0629 URL +management.services.manage.label.enabled=\u062A\u0645\u06A9\u06CC\u0646 + +management.services.manage.label.ssoParticipant=SSO \u0645\u0634\u0627\u0631\u06A9 +management.services.manage.label.usernameAttribute=\u0627\u0633\u0645 \u0627\u0644\u0645\u0633\u062A\u062E\u062F\u0645 +management.services.manage.label.anonymous=\u0645\u062C\u0647\u0648\u0644 +management.services.manage.label.attributes=\u0633\u0645\u0627\u062A +management.services.manage.label.evaluationOrder=\u0627\u0644\u0646\u0638\u0627\u0645 + +management.services.manage.action.edit=\u062A\u062D\u0631\u06CC\u0631 +management.services.manage.action.delete=\u062D\u0630\u0641 + + + +screen.unavailable.heading=\u0627\u0644\u062A\u0637\u0628\u064A\u0642 \u063A\u064A\u0631 \u0645\u062A\u0648\u0641\u0631 +screen.unavailable.message=\u0643\u0627\u0646 \u0647\u0646\u0627\u0643 \u062E\u0637\u0623 \u0645\u0627 \u0623\u062B\u0646\u0627\u0621 \u0645\u062D\u0627\u0648\u0644\u0629 \u062A\u0646\u0641\u064A\u0630 \u0637\u0644\u0628\u0643. \u0627\u0644\u0631\u062C\u0627\u0621 \u0625\u0628\u0644\u0627\u063A \u0645\u0643\u062A\u0628 \u0627\u0644\u062F\u0639\u0645 \u0623\u0648 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649. + +footer.links=\u0648\u0635\u0644\u0627\u062A \u0625\u0644\u0649 \u0627\u0644\u0645\u0648\u0627\u0631\u062F: +footer.homePage=\u0627\u0644\u0635\u0641\u062D\u0629 \u0627\u0644\u0631\u0626\u064A\u0633\u064A\u0629 +footer.wiki=\u0648\u064A\u0643\u064A +footer.issueTracker=\u0642\u0636\u064A\u0629 \u0627\u0644\u0645\u0642\u062A\u0641\u064A +footer.mailingLists=\u0627\u0644\u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u0628\u0631\u064A\u062F\u064A\u0629 +footer.copyright=\u062D\u0642 \u0627\u0644\u0646\u0634\u0631 © 2005 - 2015 Apereo, Inc. \u062C\u0645\u064A\u0639 \u0627\u0644\u062D\u0642\u0648\u0642 \u0645\u062D\u0641\u0648\u0638\u0629. +footer.poweredBy=\u0628\u062F\u0639\u0645 \u0645\u0646 Apereo Central Authentication Service {0} diff --git a/cas-management-webapp/src/main/resources/messages_ca.properties b/cas-management-webapp/src/main/resources/messages_ca.properties new file mode 100644 index 000000000000..caf1dd153658 --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_ca.properties @@ -0,0 +1,68 @@ +#Author: Evili del Rio i Silvan + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +#Logout Screen Messages +screen.logout.header=Tancament de sessi\u00db satisfactori. +screen.logout.success=Heu tancat la sessi\u00db satisfactoriament al Servei Central d'Autenticaci\u00db (CAS). + + +# SERVICES MANAGEMENT +addServiceView=Afegir un Servei Nou +editServiceView=Editar el Servei +manageServiceView=Administrar els Serveis + +registeredService.serviceId.exists=Un Servei amb la mateixa URL de servei ja existeix. + +application.title=Apereo Servei Central d'Autenticaci\u00db +application.errors.global=Si us plau corregiu els seg\u00b8ents errors: + +management.services.title=Gesti\u00db de Serveis +management.services.link.logout=Finalitzar la sessi\u00db + +management.services.status.notdeleted=El servei es pot esborra. +management.services.status.deleted={0} ha sigut esborrat correctament. + +management.services.add.instructions=si us plau, assegureu-vos de pujar les canvis fent clic al bot\u00db Desar ubicat a la part inferior de la p\u2021gina. +management.services.add.property.name=Nom +management.services.add.property.serviceUrl=URL de Servei +management.services.add.property.serviceUrl.instructions=Podeu fer servir l'estil Ant de patrons de cerca +management.services.add.property.description=Descripci\u00db +management.services.add.property.themeName=Nom del Tema +management.services.add.property.status=Estat +management.services.add.property.status.enabled=Activat + +management.services.add.property.status.ssoParticipant= Participant SSO +management.services.add.property.status.anonymousAccess=Acc\u00c8s An\u00danim +management.services.add.property.attributes=Atributs + +management.services.add.button.save=Desar canvis +management.services.add.button.cancel=Cancel\u2211lar + +management.services.manage.label.name=Nom de Servei +management.services.manage.label.serviceUrl=URL de Servei +management.services.manage.label.enabled=Activat + +management.services.manage.label.ssoParticipant=Participant SSO + +management.services.manage.action.edit=editar +management.services.manage.action.delete=esborrar diff --git a/cas-management-webapp/src/main/resources/messages_cs.properties b/cas-management-webapp/src/main/resources/messages_cs.properties new file mode 100644 index 000000000000..aa3218dbe888 --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_cs.properties @@ -0,0 +1,80 @@ +#Generated by ResourceBundle Editor (http://eclipse-rbe.sourceforge.net) +#Welcome Screen Messages + +addServiceView = P\u0159idat novou slu\u017Ebu + +application.errors.global = Opravte pros\u00EDm n\u00E1sleduj\u00EDc\u00ED chyby: +application.title = Apereo Centr\u00E1ln\u00ED Autentiza\u010Dn\u00ED Slu\u017Eba + +editServiceView = Editovat slu\u017Ebu + +footer.copyright = Copyright © 2005 - 2012 Apereo, Inc. V\u0161echna pr\u00E1va vyhrazena. +footer.homePage = \u00DAvodn\u00ED str\u00E1nka +footer.issueTracker = Syst\u00E9m pro sledov\u00E1n\u00ED chyb +footer.links = Odkazy na informace o CASu: +footer.mailingLists = E-mailov\u00E9 konference +footer.poweredBy = Provozov\u00E1no na Apereo Centr\u00E1ln\u00ED Autentiza\u010Dn\u00ED Slu\u017Eb\u011B {0} +footer.wiki = Wiki + +manageServiceView = Spr\u00E1va slu\u017Eeb + +management.services.add.button.cancel = Storno +management.services.add.button.save = Ulo\u017Eit zm\u011Bny +management.services.add.instructions = Nezapome\u0148te pros\u00EDm potvrdit proveden\u00E9 zm\u011Bny kliknut\u00EDm na tla\u010D\u00EDtlo "Ulo\u017Eit zm\u011Bny" na konci str\u00E1nky. +management.services.add.property.attributes = Atributy +management.services.add.property.description = Popis +management.services.add.property.evaluationOrder = Po\u0159ad\u00ED +management.services.add.property.ignoreAttributes = Ignorovat spr\u00E1vu atribut\u016F pomoc\u00ED tohoto n\u00E1stroje +management.services.add.property.name = N\u00E1zev +management.services.add.property.serviceUrl = URL slu\u017Eby +management.services.add.property.serviceUrl.instructions = M\u016F\u017Eete pou\u017E\u00EDt Ant-style Pattern Matching +management.services.add.property.status = Stav +management.services.add.property.status.allowedToProxy = Povoleno k proxy +management.services.add.property.status.anonymousAccess = Anonymn\u00ED p\u0159\u00EDstup +management.services.add.property.status.enabled = Povoleno +management.services.add.property.status.ssoParticipant = Sou\u010D\u00E1st SSO +management.services.add.property.themeName = N\u00E1zev t\u00E9matu +management.services.link.logout = \u00D6dhl\u00E1sit +management.services.manage.action.delete = smazat +management.services.manage.action.edit = editovat +management.services.manage.label.allowedToProxy = Povoleno proxy +management.services.manage.label.anonymous = Anonymn\u00ED +management.services.manage.label.attributes = Atributy +management.services.manage.label.enabled = Povoleno +management.services.manage.label.evaluationOrder = Po\u0159ad\u00ED +management.services.manage.label.name = N\u00E1zev slu\u017Eby +management.services.manage.label.serviceUrl = URL slu\u017Eby +management.services.manage.label.ssoParticipant = SSO +management.services.manage.label.usernameAttribute = U\u017Eivatelsk\u00E9 jm\u00E9no +management.services.service.warn = CAS moment\u00E1ln\u011B b\u011B\u017E\u00ED v "otev\u0159en\u00E9m m\u00F3du", proto\u017Ee v tomto n\u00E1stroji nejsou nakonfigurov\u00E1ny \u017E\u00E1dn\u00E9 slu\u017Eby. Jakmile v tomto n\u00E1stroji nastav\u00EDte slu\u017Ebu, nebude ji\u017E CAS pova\u017Eov\u00E1n za otev\u0159en\u00FD a v\u0161echny aplikace, kter\u00E9 budou po\u017Eadovat p\u0159ihl\u00E1\u0161en\u00ED pomoc\u00ED CASu, mus\u00ED b\u00FDt t\u00EDmto n\u00E1strojem registrov\u00E1ny. To zahrnuje i TENTO N\u00C1STROJ. Pokud budete cht\u00EDt tento n\u00E1stroj pou\u017E\u00EDvat, PRVN\u00CD SLU\u017DBA MUS\u00CD B\u00DDT TENTO N\u00C1STROJ. V\u00FDchoz\u00ED URL N\u00E1stoje pro spr\u00E1vu slu\u017Eeb je "{0}". +management.services.status.deleted = \u00DAsp\u011B\u0161n\u00E9 smaz\u00E1n\u00ED {0}. +management.services.status.evaluationOrder.notupdated = Po\u0159ad\u00ED vyhodnocen\u00ED slu\u017Eby nebylo mo\u017En\u00E9 zm\u011Bnit. +management.services.status.notdeleted = Slu\u017Ebu nebylo mo\u017En\u00E9 smazat. +management.services.title = Spr\u00E1va slu\u017Eeb + +registeredService.serviceId.exists = Slu\u017Eba s touto URL slu\u017Eby ji\u017E existuje. + +screen.blocked.header = P\u0159\u00EDstup zam\u00EDtnut +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +#Logout Screen Messages +screen.logout.header = \u00DAsp\u011B\u0161n\u00E9 odhl\u00E1\u0161en\u00ED +screen.logout.success = \u00DAsp\u011B\u0161n\u011B jste se odhl\u00E1sili od aplikace pro Spr\u00E1vu slu\u017Eeb CASu. +screen.unavailable.heading = Aplikace pro spr\u00E1vu slu\u017Eeb CASu nen\u00ED dostupn\u00E1 +screen.unavailable.message = P\u0159i vy\u0159izov\u00E1n\u00ED Va\u0161eho po\u017Eadavku do\u0161lo k chyb\u011B. Uv\u011Bdomte pros\u00EDm sv\u00E9 spr\u00E1vce nebo to zkuste znovu. diff --git a/cas-management-webapp/src/main/resources/messages_de.properties b/cas-management-webapp/src/main/resources/messages_de.properties new file mode 100644 index 000000000000..773f05a4efaa --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_de.properties @@ -0,0 +1,76 @@ +#Author: Konrad Wulf +# businessMart AG +# http://www.businessmart.de + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Blocked Errors Page +screen.blocked.header=Zugriff verweigert + +#Logout Screen Messages +screen.logout.header=Abmeldung erfolgreich +screen.logout.success=Sie haben sich erfolgreich vom Central Authentication Service abgemeldet. + +# SERVICES MANAGEMENT +addServiceView=Neuen Service hinzufügen +editServiceView=Service ändern +manageServiceView=Services Verwalten + +registeredService.serviceId.exists=Ein Service mit derselben Service Url existiert bereits + +application.title=Apereo Central Authentication Service +application.errors.global=Bitte die unten stehenden Fehler korregieren: + +management.services.title=Services Verwaltung +management.services.link.logout=Abmelden + +management.services.status.notdeleted=Der Service kann nicht gelöscht werden +management.services.status.deleted={0} wurde erfolgreich gelöscht. + +management.services.add.instructions=Bitte sicherstellen, daß die Änderungen mit dem Änderungen speichern Knopf am unteren Ende der Seite übernommen werden. +management.services.add.property.name=Name +management.services.add.property.serviceUrl=Service Url +management.services.add.property.serviceUrl.instructions=Es können Ant-style Pattern Matching Regeln verwendet werden +management.services.add.property.description=Beschreibung +management.services.add.property.themeName=Name des Themes +management.services.add.property.status=Status +management.services.add.property.status.enabled=Aktiviert + +management.services.add.property.status.ssoParticipant=SSO Teilnehmer +management.services.add.property.status.anonymousAccess=Anonymer Zugriff +management.services.add.property.attributes=Attribute +management.services.add.property.ignoreAttributes=Attribut Verwaltung durch Weboberfläche ignorieren +management.services.add.property.evaluationOrder=Reigenfolge + +management.services.add.button.save=Änderungen speichern +management.services.add.button.cancel=Abrechen + +management.services.manage.label.name=Service Name +management.services.manage.label.serviceUrl= Service Url +management.services.manage.label.enabled=Aktiviert + +management.services.manage.label.ssoParticipant=SSO Teilnehmer + +management.services.manage.action.edit=Ändern +management.services.manage.action.delete=Löschen + + diff --git a/cas-management-webapp/src/main/resources/messages_es.properties b/cas-management-webapp/src/main/resources/messages_es.properties new file mode 100644 index 000000000000..3a6d3e6f2903 --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_es.properties @@ -0,0 +1,72 @@ +#Author: Joaquin Recio, Jose Luis Huertas and Juan Paulo Soto + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Blocked Errors Page +screen.blocked.header=Acceso Denegado + +#Logout Screen Messages +screen.logout.header=Cierre de sesi\u00f3n correcto. +screen.logout.success=Ha cerrado correctamente su sesi\u00f3n del Servicio de Autentificaci\u00f3n Central. + +# SERVICES MANAGEMENT +addServiceView=Agregar Nuevo Servicio +editServiceView=Editar Servicio +manageServiceView=Administrar Servicios + +registeredService.serviceId.exists=Un Servicio con la misma URL de servicio ya existe. + +application.title=Apereo Servicio Central de Autenticaci\u00f3n +application.errors.global=Por favor corrija los siguientes errores: +management.services.title=Administrador de Servicios +management.services.link.logout=Salir + +management.services.status.notdeleted=El servicio no puede ser borrado. +management.services.status.deleted={0} ha sido borrado correctamente. + +management.services.add.instructions=Favor de asegurarse de aplicar los cambios haciendo un click en el bot\u00f3n "Guardar Cambios" ubicado en la parte inferior de la p\u00e1gina. +management.services.add.property.name=Nombre +management.services.add.property.serviceUrl=URL de Servicio +management.services.add.property.serviceUrl.instructions=Usted puede utilizar Ant-style Pattern Matching +management.services.add.property.description=Descripci\u00f3n +management.services.add.property.themeName=Nombre del Tema +management.services.add.property.status=Estado +management.services.add.property.status.enabled=Activado + +management.services.add.property.status.ssoParticipant=Participante SSO +management.services.add.property.status.anonymousAccess=Acceso An\u00f3nimo +management.services.add.property.attributes=Atributos +management.services.add.property.ignoreAttributes=Ignorar Administraci\u00f3n de Atributos a trav\u00e9s de esta Herramienta +management.services.add.property.evaluationOrder=Ordenar + +management.services.add.button.save=Guardar Cambios +management.services.add.button.cancel=Cancelar + +management.services.manage.label.name=Nombre de Servicio +management.services.manage.label.serviceUrl=URL de Servicio +management.services.manage.label.enabled=Activado + +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=editar +management.services.manage.action.delete=borrar + diff --git a/cas-management-webapp/src/main/resources/messages_fa.properties b/cas-management-webapp/src/main/resources/messages_fa.properties new file mode 100644 index 000000000000..ff7af0b3c0ea --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_fa.properties @@ -0,0 +1,88 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Blocked Errors Page +screen.blocked.header=\u062F\u0633\u062A\u0631\u0633\u06CC \u0645\u0645\u06A9\u0646 \u0646\u06CC\u0633\u062A + +#Logout Screen Messages +screen.logout.header=\u062E\u0631\u0648\u062C \u0627\u0632 \u067E\u0627\u06CC\u06AF\u0627\u0647 \u0645\u0648\u0641\u0642\u06CC\u062A \u0622\u0645\u06CC\u0632 \u0628\u0648\u062F +screen.logout.success=\u0628\u0627 \u0645\u0648\u0641\u0642\u06CC\u062A \u0627\u0632 \u067E\u0627\u06CC\u06AF\u0627\u0647 \u062A\u0627\u06CC\u06CC\u062F \u0645\u0631\u06A9\u0632\u06CC CAS \u062E\u0627\u0631\u062C \u0634\u062F\u06CC\u062F + +# SERVICES MANAGEMENT +addServiceView=\u0627\u0636\u0627\u0641\u0647 \u06A9\u0631\u062F\u0646 \u0633\u0631\u0648\u06CC\u0633 \u062C\u062F\u06CC\u062F +editServiceView=\u0648\u06CC\u0631\u0627\u06CC\u0634 \u0633\u0631\u0648\u06CC\u0633 +manageServiceView=\u0645\u062F\u06CC\u0631\u06CC\u062A \u0633\u0631\u0648\u06CC\u0633 \u0647\u0627 + +registeredService.serviceId.exists=\u0633\u0631\u0648\u06CC\u0633\u06CC \u0628\u0627 \u0627\u06CC\u0646 \u0622\u062F\u0631\u0633 \u0633\u0631\u0648\u06CC\u0633 \u062F\u0631 \u062D\u0627\u0644 \u062D\u0627\u0636\u0631 \u0648\u062C\u0648\u062F \u062F\u0627\u0631\u062F. + +application.title=Apereo Central Authentication Service +application.errors.global=\u0644\u0637\u0641\u0627\u064B \u062E\u0637\u0627\u0647\u0627\u06CC \u0632\u06CC\u0631 \u0631\u0627 \u062A\u0635\u062D\u06CC\u062D \u06A9\u0646\u06CC\u062F: + +management.services.title=\u0645\u062F\u06CC\u0631\u06CC\u062A \u0633\u0631\u0648\u06CC\u0633 \u0647\u0627 +management.services.link.logout=\u062E\u0631\u0648\u062C \u0627\u0632 \u067E\u0627\u06CC\u06AF\u0627\u0647 + +management.services.status.notdeleted=\u0627\u06CC\u0646 \u0633\u0631\u0648\u06CC\u0633 \u0631\u0627 \u0646\u0645\u06CC\u062A\u0648\u0627\u0646 \u062D\u0630\u0641 \u06A9\u0631\u062F +management.services.status.deleted={0} \u0628\u0627 \u0645\u0648\u0641\u0642\u06CC\u062A \u062D\u0630\u0641 \u0634\u062F +management.services.status.evaluationOrder.notupdated=\u062A\u063A\u06CC\u06CC\u0631\u0627\u062A \u0642\u0627\u0628\u0644 \u0630\u062E\u06CC\u0631\u0647 \u0646\u06CC\u0633\u062A\u0628\u0631\u0627\u06CC \u062B\u0628\u062A \u0634\u062F\u0646 \u062A\u063A\u06CC\u06CC\u0631\u0627\u062A \u062D\u062A\u0645\u0627\u064B \u0631\u0648\u06CC \u062F\u06A9\u0645\u0647 \u0630\u062E\u06CC\u0631\u0647 \u062A\u063A\u06CC\u06CC\u0631\u0627\u062A \u062F\u0631 \u067E\u0627\u06CC\u06CC\u0646 \u0635\u0641\u062D\u0647 \u06A9\u0644\u06CC\u06A9 \u06A9\u0646\u06CC\u062F. +management.services.add.property.name=\u0646\u0627\u0645 +management.services.add.property.serviceUrl=\u0622\u062F\u0631\u0633 \u0633\u0631\u0648\u06CC\u0633 +management.services.add.property.serviceUrl.instructions=\u0645\u06CC\u062A\u0648\u0627\u0646\u06CC\u062F \u0627\u0632 \u062A\u0637\u0628\u06CC\u0642 \u0627\u0644\u06AF\u0648\u06CC \u0634\u06CC\u0648\u0647 Ant \u0627\u0633\u062A\u0641\u0627\u062F\u0647 \u06A9\u0646\u06CC\u062F +management.services.add.property.description=\u062A\u0648\u0636\u06CC\u062D +management.services.add.property.themeName=\u0646\u0627\u0645 \u0632\u0645\u06CC\u0646\u0647 +management.services.add.property.status=\u0648\u0636\u0639\u06CC\u062A +management.services.add.property.status.enabled=\u0641\u0639\u0627\u0644 + +management.services.add.property.status.ssoParticipant=\u062E\u0631\u0648\u062C \u06CC\u06A9\u0628\u0627\u0631\u0647 +management.services.add.property.status.anonymousAccess=\u062F\u0633\u062A\u0631\u0633\u06CC \u0628\u06CC \u0646\u0627\u0645 +management.services.add.property.attributes=\u0645\u0634\u062E\u0635\u0647 \u0647\u0627 +management.services.add.property.ignoreAttributes=\u0627\u0632 \u0637\u0631\u06CC\u0642 \u0627\u06CC\u0646 \u0627\u0628\u0632\u0627\u0631 \u0627\u0632 \u0645\u0634\u062E\u0635\u0647 \u0645\u062F\u06CC\u0631\u06CC\u062A \u0635\u0631\u0641 \u0646\u0638\u0631 \u06A9\u0646\u06CC\u062F +management.services.add.property.evaluationOrder=\u062A\u0631\u062A\u06CC\u0628 + +management.services.add.button.save=\u0630\u062E\u06CC\u0631\u0647 \u062A\u063A\u06CC\u06CC\u0631\u0627\u062A +management.services.add.button.cancel=\u0644\u063A\u0648 + +management.services.manage.label.name=\u0646\u0627\u0645 \u0633\u0631\u0648\u06CC\u0633 +management.services.manage.label.serviceUrl=\u0622\u062F\u0631\u0633 \u0633\u0631\u0648\u06CC\u0633 +management.services.manage.label.enabled=\u0641\u0639\u0627\u0644 + +management.services.manage.label.ssoParticipant=\u062E\u0631\u0648\u062C \u06CC\u06A9\u0628\u0627\u0631\u0647 +management.services.manage.label.usernameAttribute=\u0634\u0646\u0627\u0633\u0647 \u06A9\u0627\u0631\u0628\u0631 +management.services.manage.label.anonymous=\u0646\u0627\u0634\u0646\u0627\u0633 +management.services.manage.label.attributes=\u0635\u0641\u0627\u062A +management.services.manage.label.evaluationOrder=\u062A\u0631\u062A\u06CC\u0628 + +management.services.manage.action.edit=\u0648\u06CC\u0631\u0627\u06CC\u0634 +management.services.manage.action.delete=\u062D\u0630\u0641 + + + +screen.unavailable.heading=\u0627\u06CC\u0646 \u0633\u0631\u0648\u06CC\u0633 \u062F\u0631 \u062D\u0627\u0644 \u062D\u0627\u0636\u0631 \u062F\u0631 \u062F\u0633\u062A\u0631\u0633 \u0646\u06CC\u0633\u062A +screen.unavailable.message=\u0628\u0647 \u0647\u0646\u06AF\u0627\u0645 \u067E\u0631\u062F\u0627\u0632\u0634 \u062A\u0642\u0627\u0636\u0627\u06CC \u0634\u0645\u0627 \u0645\u0634\u06A9\u0644\u06CC \u0628\u0647 \u0648\u062C\u0648\u062F \u0622\u0645\u062F. \u0644\u0637\u0641\u0627 \u0628\u0627 \u067E\u0634\u062A\u06CC\u0628\u0627\u0646\u06CC \u062A\u0645\u0627\u0633 \u06AF\u0631\u0641\u062A\u0647 \u0648 \u06CC\u0627 \u062F\u0648\u0628\u0627\u0631\u0647 \u0633\u0639\u06CC \u06A9\u0646\u06CC\u062F + +footer.links=\u0644\u06CC\u0646\u06A9 \u0647\u0627\u06CC \u0645\u0631\u0628\u0648\u0637 \u0628\u0647 CAS +footer.homePage=\u0635\u0641\u062D\u0647 \u0627\u0648\u0644 +footer.wiki=\u0648\u06CC\u06A9\u06CC +footer.issueTracker=\u06AF\u0632\u0627\u0631\u0634 \u0627\u0634\u06A9\u0627\u0644 +footer.mailingLists=\u0644\u06CC\u0633\u062A \u0647\u0627\u06CC \u0645\u06A9\u0627\u062A\u0628\u0647 +footer.copyright=\u062D\u0642 \u0646\u0634\u0631 © 2005 - 2015 Apereo, Inc. \u06A9\u0644\u06CC\u0647 \u062D\u0642\u0648\u0642 \u0645\u062D\u0641\u0648\u0638 \u0627\u0633\u062A +footer.poweredBy=\u067E\u0634\u062A\u06CC\u0628\u0627\u0646\u06CC \u062A\u0648\u0633\u0637Apereo Central Authentication Service {0} diff --git a/cas-management-webapp/src/main/resources/messages_fr.properties b/cas-management-webapp/src/main/resources/messages_fr.properties new file mode 100644 index 000000000000..b416992b0112 --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_fr.properties @@ -0,0 +1,89 @@ +#Author: Pascal Aubry +#Version: $Revision$ $Date$ +#Since: 3.0.4 + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Blocked Errors Page +screen.blocked.header=Accès non autorisé + +#Logout Screen Messages +screen.logout.header=Déconnexion réussie +screen.logout.success=Vous vous êtes déconnecté(e) de l'application d'administration du CAS + +# SERVICES MANAGEMENT +addServiceView=Ajouter un nouveau service +editServiceView=Editer le service +manageServiceView=Gérer les services + +registeredService.serviceId.exists=Un service avec cette URL existe déjà. + +application.title=Service Central d'Authentification Apereo +application.errors.global=Veuillez corriger les erreurs ci-dessous : + +management.services.title=Gestion des services +management.services.link.logout=Déconnexion + +management.services.status.notdeleted=Le service ne peut pas être supprimé. +management.services.status.deleted={0} a été supprimé. +management.services.status.evaluationOrder.notupdated=L'ordre des services n'a pas pu être mis à jour. + +management.services.add.instructions=Veuillez confirmer vos changements en cliquant sur le bouton Sauvegarder les modifications au bas de la page +management.services.add.property.name=Nom +management.services.add.property.serviceUrl=URL du service +management.services.add.property.serviceUrl.instructions=Vous pouvez utiliser des expressions comme dans Ant +management.services.add.property.description=Description +management.services.add.property.themeName=Nom du thème +management.services.add.property.status=Etat +management.services.add.property.status.enabled=Activé + +management.services.add.property.status.ssoParticipant=Participant au SSO +management.services.add.property.status.anonymousAccess=Accès anonyme +management.services.add.property.attributes=Attributs +management.services.add.property.ignoreAttributes=Ignorer la gestion des attributs de cet outil +management.services.add.property.evaluationOrder=Ordre + +management.services.add.button.save=Sauvegarder les modifications +management.services.add.button.cancel=Annuler + +management.services.manage.label.name=Nom du service +management.services.manage.label.serviceUrl=URL du service +management.services.manage.label.enabled=Activé + +management.services.manage.label.ssoParticipant=SSO +management.services.manage.label.usernameAttribute=Nom d'utilisateur +management.services.manage.label.anonymous=Anonyme +management.services.manage.label.attributes=Attributs +management.services.manage.label.evaluationOrder=Ordre + +management.services.manage.action.edit=éditer +management.services.manage.action.delete=supprimer + +screen.unavailable.heading=L'application d'administration de CAS est indisponible +screen.unavailable.message=Une erreur s'est produite. Merci de contacter votre support ou de réessayer. +footer.links=Liens vers les ressources CAS : +footer.homePage=Page d'accueil +footer.wiki=Documentation +footer.issueTracker=Suivi des bugs/évolutions +footer.mailingLists=Liste de diffusion +footer.copyright=Copyright © 2005 - 2015 Apereo, Inc. Tous droits réservés. +footer.poweredBy=Proposé par Apereo Central Authentication Service {0} diff --git a/cas-management-webapp/src/main/resources/messages_hr.properties b/cas-management-webapp/src/main/resources/messages_hr.properties new file mode 100644 index 000000000000..5eb3caad78c3 --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_hr.properties @@ -0,0 +1,68 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# @author Nebojsa Topolscak +# @author Jasmina Plavac +# University Computing Center - Zagreb, Croatia +# @since 3.1.1 + +#Logout Screen Messages +screen.logout.header=Uspje\u0161na odjava +screen.logout.success=Uspje\u0161no ste se odjavili iz Centralnog autentikacijskog servisa + +# SERVICES MANAGEMENT +addServiceView=Dodaj novi servis +editServiceView=Uredi servis +manageServiceView=Upravljanje servisima + +registeredService.serviceId.exists=Servis s ovim URL-om ve\u0107 postoji. + +application.title=Apereo Centralni Autentikacijski Servis +application.errors.global=Molimo vas da ispravite ove pogre\u0161ke: + +management.services.title=Upravljanje servisima +management.services.link.logout=Odjava + +management.services.status.notdeleted=Servis se ne mo\u017ee obrisati. +management.services.status.deleted={0} je uspje\u0161no obrisan. + +management.services.add.instructions=Ne zaboravite spremiti promjene pritiskom na dugme "Spremi promjene", koji se nalazi na dnu stranice. +management.services.add.property.name=Naziv +management.services.add.property.serviceUrl=URL servisa +management.services.add.property.serviceUrl.instructions=Mo\u017eete rabiti regularne izraze kakve rabi Ant. +management.services.add.property.description=Opis +management.services.add.property.themeName=Naziv teme +management.services.add.property.status=Status +management.services.add.property.status.enabled=Omogu\u0107en + +management.services.add.property.status.ssoParticipant=SSO Participant +management.services.add.property.status.anonymousAccess=Anonimni pristup +management.services.add.property.attributes=Atributi + +management.services.add.button.save=Spremi promjene +management.services.add.button.cancel=Odustani + +management.services.manage.label.name=Naziv servisa +management.services.manage.label.serviceUrl=URL servisa +management.services.manage.label.enabled=Omogu\u0107eno + +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=Uredi +management.services.manage.action.delete=Obri\u0161i diff --git a/cas-management-webapp/src/main/resources/messages_it.properties b/cas-management-webapp/src/main/resources/messages_it.properties new file mode 100644 index 000000000000..8e3f1745182f --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_it.properties @@ -0,0 +1,76 @@ +#Author: Roberto Cosenza http://robcos.com +#Version: $Revision$ $Date$ +#Since: 3.0.5 + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Blocked Errors Page +screen.blocked.header=Accesso Negato + +#Logout Screen Messages +screen.logout.header=Logout effettuato con successo +screen.logout.success=Hai correttamente effettuato il logout dal Central Authentication Service. + +# SERVICES MANAGEMENT +addServiceView=Aggiungere nuovo servizio +editServiceView=Modificare servizio +manageServiceView=Gestire i servizi + +registeredService.serviceId.exists=Un servizio con questa URL esiste già. + +application.title=Apereo Central Authentication Service +application.errors.global=Si prega di correggere i seguenti errori: + +management.services.title=Gestione servizi +management.services.link.logout=Disconnetti + +management.services.status.notdeleted=Il servizio non può essere eliminato. +management.services.status.deleted={0} è stato eliminato correttamente. + +management.services.add.instructions=Si prega di assicurarsi che le modifiche siano salvate cliccando il bottone Salva al fondo della pagina. +management.services.add.property.name=Nome +management.services.add.property.serviceUrl=URL servizio +management.services.add.property.serviceUrl.instructions=È possibile utilizzare il Pattern Matching in stile Ant +management.services.add.property.description=Descrizione +management.services.add.property.themeName=Nome tema +management.services.add.property.status=Stato +management.services.add.property.status.enabled=Abilitato + +management.services.add.property.status.ssoParticipant=Partecipante SSO +management.services.add.property.status.anonymousAccess=Accesso anonimo +management.services.add.property.attributes=Attributi +management.services.add.property.ignoreAttributes=Ignorare la gestione degli attributi attraverso questa interfaccia di gestione +management.services.add.property.evaluationOrder=Ordinare + +management.services.add.button.save=Salva +management.services.add.button.cancel=Cancella + +management.services.manage.label.name=Nome servizio +management.services.manage.label.serviceUrl=URL servizio +management.services.manage.label.enabled=Abilitato + +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=modifica +management.services.manage.action.delete=elimina + + diff --git a/cas-management-webapp/src/main/resources/messages_ja.properties b/cas-management-webapp/src/main/resources/messages_ja.properties new file mode 100644 index 000000000000..a75f62ff1b83 --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_ja.properties @@ -0,0 +1,40 @@ +#Author: Shoji Kajita +#Version: $Revision$ $Date$ +#Since: 3.1 + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +#Logout Screen Messages +screen.logout.header=\u30ed\u30b0\u30a2\u30a6\u30c8\u3057\u307e\u3057\u305f +screen.logout.success=Central Authentication Service \u3092\u30ed\u30b0\u30a2\u30a6\u30c8\u3067\u304d\u307e\u3057\u305f\uff0e + +### SERVICES MANAGEMENT +addServiceView=\u65b0\u3057\u3044\u30b5\u30fc\u30d3\u30b9\u3092\u8ffd\u52a0 +editServiceView=\u30b5\u30fc\u30d3\u30b9\u3092\u7de8\u96c6 +manageServiceView=\u30b5\u30fc\u30d3\u30b9\u3092\u7ba1\u7406 + +services.manage.status.notdeleted=\u30b5\u30fc\u30d3\u30b9\u306f\u524a\u9664\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093\uff0e +services.manage.status.deleted={0} \u304c\u524a\u9664\u3055\u308c\u307e\u3057\u305f\uff0e +services.manage.status.enabled=ServiceRegistry \u306f\u6709\u52b9\u306b\u306a\u308a\u307e\u3057\u305f\uff0e +services.manage.status.disabled=ServiceRegistry \u306f\u7121\u52b9\u306b\u306a\u308a\u307e\u3057\u305f\uff0e + +registeredService.serviceId.exists=\u540c\u3058\u30b5\u30fc\u30d3\u30b9 URL \u3092\u3082\u3064\u30b5\u30fc\u30d3\u30b9\u304c\u304c\u3059\u3067\u306b\u5b58\u5728\u3057\u3066\u3044\u307e\u3059\uff0e diff --git a/cas-management-webapp/src/main/resources/messages_mk.properties b/cas-management-webapp/src/main/resources/messages_mk.properties new file mode 100644 index 000000000000..62c756b4fdb7 --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_mk.properties @@ -0,0 +1,67 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# @author \u0412\u0430\u043d\u0433\u0435\u043b \u0410\u0458\u0430\u043d\u043e\u0432\u0441\u043a\u0438 +# \u0418\u043d\u0441\u0442\u0438\u0442\u0443\u0442 \u0437\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0442\u0438\u043a\u0430 +# @since 3.1.1 + +#Logout Screen Messages +screen.logout.header=\u0423\u0441\u043f\u0435\u0448\u043d\u0430 \u043e\u0434\u0458\u0430\u0432\u0430 +screen.logout.success=\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0435 \u043e\u0434\u0458\u0430\u0432\u0438\u0432\u0442\u0435 \u043e\u0434 \u0426\u0435\u043d\u0442\u0440\u0430\u043b\u043d\u0438\u043e\u0442 \u0410\u0432\u0442\u0435\u0442\u0438\u043a\u0430\u0446\u0438\u0441\u043a\u0438 \u0421\u0435\u0440\u0432\u0438\u0441. + +# SERVICES MANAGEMENT +addServiceView=\u0414\u043e\u0434\u0430\u0434\u0438 \u043d\u043e\u0432 \u0441\u0435\u0440\u0432\u0438\u0441 +editServiceView=\u0423\u0440\u0435\u0434\u0438 \u0441\u0435\u0440\u0432\u0438\u0441 +manageServiceView=\u0423\u043f\u0440\u0430\u0432\u0443\u0432\u0430\u045a\u0435 \u0441\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0438 + +registeredService.serviceId.exists=\u0421\u0435\u0440\u0432\u0438\u0441 \u0441\u043e \u043e\u0432\u043e\u0458 URL \u0432\u0435\u045c\u0435 \u043d\u0435 \u043f\u043e\u0441\u0442\u043e\u0438. + +application.title=Apereo \u0426\u0435\u043d\u0442\u0440\u0430\u043b\u0435\u043d \u0410\u0432\u0442\u0435\u043d\u0442\u0438\u043a\u0430\u0446\u0438\u0441\u043a\u0438 \u0421\u0435\u0440\u0432\u0438\u0441 +application.errors.global=\u0412\u0435 \u043c\u043e\u043b\u0438\u043c\u0435 \u0434\u0430 \u0433\u0438 \u0438\u0441\u043f\u0440\u0430\u0432\u0438\u0442\u0435 \u043e\u0432\u0438\u0435 \u0433\u0440\u0435\u0448\u043a\u0438: + +management.services.title=\u0423\u043f\u0440\u0430\u0432\u0443\u0432\u0430\u045a\u0435 \u0441\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0438 +management.services.link.logout=\u041e\u0434\u0458\u0430\u0432\u0430 + +management.services.status.notdeleted=\u0421\u0435\u0440\u0432\u0438\u0441\u043e\u0442 \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0441\u0435 \u0438\u0437\u0431\u0440\u0438\u0448\u0435. +management.services.status.deleted={0} \u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0438\u0437\u0431\u0440\u0438\u0448\u0430\u043d. + +management.services.add.instructions=\u041d\u0435 \u0437\u0430\u0431\u043e\u0440\u0430\u0432\u0430\u0458\u0442\u0435 \u0434\u0430 \u0433\u043e \u0437\u0430\u0447\u0443\u0432\u0430\u0442\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u0435 \u0441\u043e \u043f\u0440\u0438\u0442\u0438\u0441\u043a\u0430\u045a\u0435 \u043d\u0430 \u043a\u043e\u043f\u0447\u0435\u0442\u043e "\u0417\u0430\u0447\u0443\u0432\u0430\u0458 \u0438\u0437\u043c\u0435\u043d\u0438", \u043a\u043e\u0435 \u0441\u0435 \u043d\u0430\u043e\u0453\u0430 \u043d\u0430 \u0434\u043d\u043e\u0442\u043e \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430. +management.services.add.property.name=\u041d\u0430\u0437\u0438\u0432 +management.services.add.property.serviceUrl=URL \u043d\u0430 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0442 +management.services.add.property.serviceUrl.instructions=\u041c\u043e\u0436\u0435\u0442\u0435 \u0434\u0430 \u043a\u043e\u0440\u0438\u0441\u0442\u0438\u0442\u0435 \u0440\u0435\u0433\u0443\u043b\u0430\u0440\u043d\u0438 \u0438\u0437\u0440\u0430\u0437\u0438 \u043a\u0430\u043a\u0432\u0438 \u0448\u0442\u043e \u0443\u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0432\u0430 Ant. +management.services.add.property.description=\u041e\u043f\u0438\u0441 +management.services.add.property.themeName=\u041d\u0430\u0437\u0438\u0432 \u043d\u0430 \u0442\u0435\u043c\u0430\u0442\u0430 +management.services.add.property.status=\u0421\u0442\u0430\u0442\u0443\u0441 +management.services.add.property.status.enabled=\u041e\u0432\u043e\u0437\u043c\u043e\u0436\u0435\u043d + +management.services.add.property.status.ssoParticipant=SSO \u0423\u0447\u0435\u0441\u043d\u0438\u043a +management.services.add.property.status.anonymousAccess=\u0410\u043d\u043e\u043d\u0438\u043c\u0435\u043d \u043f\u0440\u0438\u0441\u0442\u0430\u043f +management.services.add.property.attributes=\u0410\u0442\u0440\u0438\u0431\u0443\u0442\u0438 + +management.services.add.button.save=\u0417\u0430\u0447\u0443\u0432\u0430\u0458 \u0438\u0437\u043c\u0435\u043d\u0438 +management.services.add.button.cancel=\u041e\u0442\u043a\u0430\u0436\u0438 + +management.services.manage.label.name=\u041d\u0430\u0437\u0438\u0432 \u043d\u0430 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0442 +management.services.manage.label.serviceUrl=URL \u043d\u0430 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0442 +management.services.manage.label.enabled=\u041e\u0432\u043e\u0437\u043c\u043e\u0436\u0435\u043d\u043e + +management.services.manage.label.ssoParticipant=SSO \u0423\u0447\u0435\u0441\u043d\u0438\u043a + +management.services.manage.action.edit=\u0418\u0437\u043c\u0435\u043d\u0438 +management.services.manage.action.delete=\u0411\u0440\u0438\u0448\u0438 diff --git a/cas-management-webapp/src/main/resources/messages_nl.properties b/cas-management-webapp/src/main/resources/messages_nl.properties new file mode 100644 index 000000000000..5c7a2ee4a5d8 --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_nl.properties @@ -0,0 +1,69 @@ +#Author: Jan "Velpi" Van der Velpen +#Version $Revision$ $Date$ +#Since 3.0.3 + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +#Logout Screen Messages +screen.logout.header=Succesvol uitgelogd. +screen.logout.success=Je bent nu uitgelogd bij de Central Authentication Service. + +# SERVICES MANAGEMENT +addServiceView=Service Toevoegen +editServiceView=Service Bewerken +manageServiceView=Services Beheren + +registeredService.serviceId.exists=Er bestaat al een Service met die Service Url. + +application.title=Apereo Central Authentication Service +application.errors.global=Gelieve onderstaande fouten te verbeteren: + +management.services.title=Service Beheer +management.services.link.logout=Uitloggen + +management.services.status.notdeleted=De service kon niet worden verwijderd. +management.services.status.deleted={0} is met succes verwijderd. + +management.services.add.instructions=Bevestig je wijzigingen door onderaan op deze pagina op Wijzigingen Opslaan te klikken. +management.services.add.property.name=Naam +management.services.add.property.serviceUrl=Service URL +management.services.add.property.serviceUrl.instructions=Je kan Ant-style Pattern Matching gebruiken. +management.services.add.property.description=Omschrijving +management.services.add.property.themeName=Naam Thema +management.services.add.property.status=Status +management.services.add.property.status.enabled=Enabled + +management.services.add.property.status.ssoParticipant=SSO Deelnemer +management.services.add.property.status.anonymousAccess=Annonieme toegang +management.services.add.property.attributes=Attributen + +management.services.add.button.save=Wijzigingen Opslaan +management.services.add.button.cancel=Annuleren + +management.services.manage.label.name=Service Naam +management.services.manage.label.serviceUrl=Service URL +management.services.manage.label.enabled=Enabled + +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=bewerken +management.services.manage.action.delete=verwijderen diff --git a/cas-management-webapp/src/main/resources/messages_pl.properties b/cas-management-webapp/src/main/resources/messages_pl.properties new file mode 100644 index 000000000000..5adf71c710fb --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_pl.properties @@ -0,0 +1,97 @@ +# @author Maja Gorecka-Wolniewicz +# @since 3.1.1 + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Blocked Errors Page +screen.blocked.header=Dost\u0119p zabroniony +screen.blocked.message=Nie mo\u017cesz korzysta\u0107 z us\u0142ugi. Prosz\u0119 skontaktowa\u0107 si\u0119 z pomoc\u0105 techniczn\u0105 w celu uzyskania dost\u0119pu. + +#Logout Screen Messages +screen.logout.header=Udane wylogowanie +screen.logout.success=Wylogowa\u0142e\u015b si\u0119 z CAS - Centralnej Us\u0142ugi Uwierzytelniania. + +# SERVICES MANAGEMENT +addServiceView=Dodaj now\u0105 us\u0142ug\u0119 +editServiceView=Edytuj us\u0142ug\u0119 +manageServiceView=Zarz\u0105dzaj us\u0142ugami + +registeredService.serviceId.exists=Us\u0142uga o takim adresie ju\u017c istnieje. +registeredService.usernameAttribute.notAvailable=Nie mo\u017cna opublikowa\u0107 wybranego atrybutu nazwy u\u017cytkownika. +registeredService.description.length=Opis wykracza poza dopuszczaln\u0105 liczb\u0119 znak\u00f3w. + +application.title=Us\u0142uga Centralnego Uwierzytelniania (Apereo CAS) +application.errors.global=Popraw poni\u017csze b\u0142\u0119dy: + +management.services.title=Zarz\u0105dzanie us\u0142ugami +management.services.link.logout=Wylogowanie + +management.services.status.notdeleted=Nie mo\u017cna usun\u0105\u0107 us\u0142ugi. +management.services.status.deleted=Usuni\u0119to {0}. +management.services.status.evaluationOrder.notupdated=Nie mo\u017cna zmieni\u0107 kolejno\u015bci przegl\u0105dania us\u0142ug. + +management.services.add.instructions=Potwierd\u017a ch\u0119\u0107 zachowania zmian naciskaj\u0105c przycisk Zachowaj zmiany +management.services.add.property.name=Nazwa +management.services.add.property.serviceUrl=Adres us\u0142ugi (URL) +management.services.add.property.serviceUrl.instructions=Mo\u017cesz u\u017cy\u0107 metody dopasowywania wzorc\u00f3w Ant +management.services.add.property.description=Opis +management.services.add.property.themeName=Nazwa motywu +management.services.add.property.status=Status +management.services.add.property.status.enabled=W\u0142\u0105czony + +management.services.add.property.status.ssoParticipant=Uczestnik systemu pojedynczego logowania +management.services.add.property.status.anonymousAccess=Dost\u0119p anonimowy +management.services.add.property.attributes=Atrybuty +management.services.add.property.evaluationOrder=Kolejno\u015b\u0107 + +management.services.add.button.save=Zachowaj zmiany +management.services.add.button.cancel=Anuluj + +management.services.manage.label.name=Nazwa us\u0142ugi +management.services.manage.label.serviceUrl= Adres us\u0142ugi (URL) +management.services.manage.label.enabled=W\u0142\u0105czony + +management.services.manage.label.ssoParticipant=SSO +management.services.manage.label.usernameAttribute=Nazwa u\u017cytkownika +management.services.manage.label.anonymous=Anonimowy +management.services.manage.label.attributes=Atrybuty +management.services.manage.label.evaluationOrder=Kolejno\u015b\u0107 + +management.services.manage.action.edit=edytuj +management.services.manage.action.delete=usu\u0144 + +management.services.service.warn=Rejestr us\u0142ug CAS jest pusty i nie zawiera \u017cadnej definicji. \ +Aplikacja, kt\u00f3re chcia\u0142eyby zosta\u0107 uwierzytelnione w CAS musz\u0105 by\u0107 zarejestrowane w rejestrze us\u0142ug. \ +Dotyczy to tak\u017ce TEJ APLIKACJI. Je\u015bli chcesz jej u\u017cywa\u0107, to PIERWSZ\u0104, KT\u00d3R\u0104 NALE\u017bY DODA\u0104 JEST ONA SAMA. \ +Domy\u015blny odno\u015bnik do Narz\u0119dzia Zarz\u0105dzania Us\u0142ugami to "{0}". + +screen.unavailable.heading=Zarz\u0105dzanie Us\u0142ugami Centralnego Uwierzytelniania (Apereo CAS) jest niedost\u0119pne +screen.unavailable.message=Wyst\u0105pi\u0142 b\u0142\u0105d podczas obs\u0142ugi zlecenia. \ +Prosz\u0119 spr\u00f3bowa\u0107 jeszcze raz lub skontaktowa\u0107 si\u0119 z pomoc\u0105 techniczn\u0105. + +footer.links=Odno\u015bniki do zasob\u00f3w CAS: +footer.homePage=Strona domowa +footer.wiki=Wiki +footer.issueTracker=\u015aledzenie zg\u0142osze\u0144 +footer.mailingLists=Listy dyskusyjne +footer.copyright=Copyright © 2005 - 2015 Apereo, Inc. Wszelkie prawa zastrze\u017cone. +footer.poweredBy=Powered by Apereo Central Authentication Service {0} diff --git a/cas-management-webapp/src/main/resources/messages_pt_BR.properties b/cas-management-webapp/src/main/resources/messages_pt_BR.properties new file mode 100644 index 000000000000..eb28cf58d663 --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_pt_BR.properties @@ -0,0 +1,67 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +#Logout Screen Messages +screen.logout.header=Sucesso ao se deslogar +screen.logout.success=Voc\u00ea se deslogou com sucesso no Servi\u00e7o de Autentica\u00e7\u00e3o Central. + +# SERVICES MANAGEMENT +addServiceView=Adicionar Novo Servi\u00e7o +editServiceView=Editar Servi\u00e7o +manageServiceView=Gerenciar Servi\u00e7os + +registeredService.serviceId.exists=Um servi\u00e7o com essa URL de servi\u00e7o j\u00e1 existe. + +application.title=Apereo Servi\u00e7o de Autentica\u00e7\u00e3o Central +application.errors.global=Por favor, corrija os erros abaixo: + +management.services.title=Gerenciar Servi\u00e7os +management.services.link.logout=Deslogar + +management.services.status.notdeleted=O servi\u00e7o n\u00e3o pode ser exclu\u00eddo. +management.services.status.deleted={0} foi exclu\u00eddo com sucesso. + +management.services.add.instructions=Por favor, certifique-se de enviar as mudan\u00e7as clicando no bot\u00e3o Salvar Mudan\u00e7as no rodap\u00e9 da p\u00e1gina +management.services.add.property.name=Nome +management.services.add.property.serviceUrl=URL do Servi\u00e7o +management.services.add.property.serviceUrl.instructions=Voc\u00ea pode usar Casamento de Padr\u00e3o no estilo Ant +management.services.add.property.description=Descri\u00e7\u00e3o +management.services.add.property.themeName=Nome do Tema +management.services.add.property.status=Estado +management.services.add.property.status.enabled=Habilitado + +management.services.add.property.status.ssoParticipant=Participante SSO +management.services.add.property.status.anonymousAccess=Acesso An\u00f4nimo +management.services.add.property.attributes=Atributos +management.services.add.property.ignoreAttributes=Ignore o Gerenciamento de Atributos por meio desta ferramenta +management.services.add.property.evaluationOrder=Pedido + +management.services.add.button.save=Salvar Mudan\u00e7as +management.services.add.button.cancel=Cancelar + +management.services.manage.label.name=Nome do Servi\u00e7o +management.services.manage.label.serviceUrl= URL do Servi\u00e7o +management.services.manage.label.enabled=Habilitado + +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=editar +management.services.manage.action.delete=excluir diff --git a/cas-management-webapp/src/main/resources/messages_pt_PT.properties b/cas-management-webapp/src/main/resources/messages_pt_PT.properties new file mode 100644 index 000000000000..274da8aa6ca2 --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_pt_PT.properties @@ -0,0 +1,72 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Blocked Errors Page +screen.blocked.header=Accesso Bloqueado + +#Logout Screen Messages +screen.logout.header=Sess\u00e3o terminada com sucesso. +screen.logout.success=A sua sess\u00e3o no Servi\u00e7o de Autentica\u00e7\u00e3o Central foi terminada com sucesso. + +# SERVICES MANAGEMENT +addServiceView=Adicionar Servi\u221a\u00dfo +editServiceView=Editar Servi\u221a\u00dfo +manageServiceView=Gerir Servi\u221a\u00dfos + +registeredService.serviceId.exists=Um servi\u221a\u00dfo com o URL de servi\u221a\u00dfo dado j\u221a\u00b0 existe + +application.title=Servi\u221a\u00dfo de Autentica\u221a\u00df\u221a\u00a3o Central Apereo +application.errors.global=Por favor corrija os erros listados em baixo: + +management.services.title=Gest\u221a\u00a3o de Servi\u221a\u00dfos +management.services.link.logout=Sair + +management.services.status.notdeleted=O servi\u221a\u00dfo n\u221a\u00a3o pode ser apagado. +management.services.status.deleted={0} foi apagado con sucesso. + +management.services.add.instructions=Por favor certifique-se do envio das altera\u221a\u00df\u221a\u00b5es carregando no bot\u221a\u00a3o Gravar Altera\u221a\u00df\u221a\u00b5es no fundo desta p\u221a\u00b0gina +management.services.add.property.name=Nome +management.services.add.property.serviceUrl=URL do Servi\u221a\u00dfo +management.services.add.property.serviceUrl.instructions=Ant-style Pattern Matching pode ser usado +management.services.add.property.description=Descri\u221a\u00df\u221a\u00a3o +management.services.add.property.themeName=Nome do tema +management.services.add.property.status=Estado +management.services.add.property.status.enabled=Activo + +management.services.add.property.status.ssoParticipant=Participante SSO +management.services.add.property.status.anonymousAccess=Accesso An\u221a\u2265nimo +management.services.add.property.attributes=Atributos +management.services.add.property.ignoreAttributes=Ignorar gest\u221a\u00a3o de atributos atrav\u221a\u00a9s desta ferramenta +management.services.add.property.evaluationOrder=Ordem + +management.services.add.button.save=Gravar altera\u221a\u00df\u221a\u00b5es +management.services.add.button.cancel=Cancelar + +management.services.manage.label.name=Nome do Servi\u221a\u00dfo +management.services.manage.label.serviceUrl= URL do Servi\u221a\u00dfo +management.services.manage.label.enabled=Enabled + +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=editar +management.services.manage.action.delete=apagar + + diff --git a/cas-management-webapp/src/main/resources/messages_ru.properties b/cas-management-webapp/src/main/resources/messages_ru.properties new file mode 100644 index 000000000000..907e8d504754 --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_ru.properties @@ -0,0 +1,60 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +#Logout Screen Messages +screen.logout.header=\u0412\u044b\u0445\u043e\u0434 \u0438\u0437 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0443\u0441\u043f\u0435\u0448\u0435\u043d. +screen.logout.success=\u0412\u044b \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0432\u044b\u0448\u043b\u0438 \u0438\u0437 \u0441\u0438\u0441\u0442\u0435\u043c\u044b "Central Authentication Service". + +management.services.link.logout=\u0412\u044b\u0439\u0442\u0438 +management.services.title=\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c\u0438 +management.services.status.deleted={0} \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0443\u0434\u0430\u043b\u0451\u043d. + +management.services.add.button.cancel=\u041e\u0442\u043c\u0435\u043d\u0430 +management.services.add.button.save=\u0421\u043e\u0445\u0440\u0430\u043d\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f +management.services.add.instructions=\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430 \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u044b \u043b\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f, \u043d\u0430\u0436\u0430\u0432 \u043d\u0430 \u043a\u043d\u043e\u043f\u043a\u0443 "\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f" \u0432\u043d\u0438\u0437\u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b +management.services.add.property.attributes=\u0410\u0442\u0442\u0440\u0438\u0431\u0443\u0442\u044b +management.services.add.property.description=\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 +management.services.add.property.evaluationOrder=\u041f\u043e\u0440\u044f\u0434\u043e\u043a +management.services.add.property.ignoreAttributes=\u041d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0430\u0442\u0442\u0440\u0438\u0431\u0443\u0442\u0430\u043c\u0438 +management.services.add.property.name=\u0418\u043c\u044f +management.services.add.property.serviceUrl=URL \u0441\u0435\u0440\u0432\u0438\u0441\u0430 +management.services.add.property.serviceUrl.instructions=\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u044b\u0435 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0432 \u0441\u0442\u0438\u043b\u0435 Ant +management.services.add.property.status=\u0421\u0442\u0430\u0442\u0443\u0441 + +management.services.add.property.status.anonymousAccess=\u0410\u043d\u043e\u043d\u0438\u043c\u043d\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f +management.services.add.property.status.enabled=\u0410\u043a\u0442\u0438\u0432\u043d\u043e +management.services.add.property.status.ssoParticipant=\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a SSO +management.services.add.property.themeName=\u0418\u043c\u044f \u0442\u0435\u043c\u044b +management.services.manage.action.delete=\u0443\u0434\u0430\u043b\u0438\u0442\u044c +management.services.manage.action.edit=\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c + +management.services.manage.label.enabled=\u0412\u043a\u043b\u044e\u0447\u0435\u043d\u043e +management.services.manage.label.name=\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 +management.services.manage.label.serviceUrl=URL \u0441\u0435\u0440\u0432\u0438\u0441\u0430 +management.services.manage.label.ssoParticipant=\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a SSO +management.services.status.notdeleted=\u0421\u0435\u0440\u0432\u0438\u0441 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0443\u0434\u0430\u043b\u0451\u043d. +manageServiceView=\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c\u0438 +registeredService.serviceId.exists=\u0421\u0435\u0440\u0432\u0438\u0441 \u0441 \u0442\u0430\u043a\u0438\u043c URL \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 +screen.blocked.header=\u0414\u043e\u0441\u0442\u0443\u043f \u0437\u0430\u043f\u0440\u0435\u0449\u0451\u043d +addServiceView=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 +application.errors.global=\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0438\u0441\u043f\u0440\u0430\u0432\u044c\u0442\u0435 \u043f\u0440\u0438\u0432\u0435\u0434\u0451\u043d\u043d\u044b\u0435 \u043d\u0438\u0436\u0435 \u043e\u0448\u0438\u0431\u043a\u0438 +application.title=Apereo Central Authentication Service +editServiceView=\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0438\u0441 diff --git a/cas-management-webapp/src/main/resources/messages_sl.properties b/cas-management-webapp/src/main/resources/messages_sl.properties new file mode 100644 index 000000000000..a16c5d94282c --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_sl.properties @@ -0,0 +1,24 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +#Logout Screen Messages +screen.logout.header=Odjava uspela +screen.logout.success=Uspe\u0161no ste se prijavili v Centralno Avtenikacijsko Storitev. diff --git a/cas-management-webapp/src/main/resources/messages_sv.properties b/cas-management-webapp/src/main/resources/messages_sv.properties new file mode 100644 index 000000000000..d9021c29c77b --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_sv.properties @@ -0,0 +1,69 @@ +#Author: Fredrik Nilsson http://www.infoflexconnect.se +#Updated 2006-08-29: PÃ¥l Axelsson & Veronika Berglund IT Support Department at Uppsala University http://www.uu.se +#Updated 2007-06-21: PÃ¥l Axelsson IT Support Department at Uppsala University http://www.uu.se + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +#Logout Screen Messages +screen.logout.header=Du har loggat ut! +screen.logout.success=Du har loggat ut frÃ¥n den centrala autentiseringstjänsten CAS. + +# SERVICES MANAGEMENT +addServiceView=Lägg till ny webbtjänst +editServiceView=Ändra webbtjänst +manageServiceView=Hantera webbtjänst + +registeredService.serviceId.exists=En webbtjänst med denna webbadress finns redan. + +application.title=Apereo Central Authentication Service +application.errors.global=Rätta felen nedan: + +management.services.title=Webbtjänstehantering +management.services.link.logout=LOGGA UT + +management.services.status.notdeleted=Webbtjänsten kan inte tas bort. +management.services.status.deleted=Webbtjänsten {0} har blivit borttagen. + +management.services.add.instructions=Kom ihÃ¥g att klicka pÃ¥ knappen SPARA lägst ner pÃ¥ sidan för att spara ändringarna. +management.services.add.property.name=Webbtjänst +management.services.add.property.serviceUrl=Webbadress +management.services.add.property.serviceUrl.instructions=Du kan använda matchningsregler av typen ANT. +management.services.add.property.description=Beskrivning +management.services.add.property.themeName=Tema +management.services.add.property.status=Status +management.services.add.property.status.enabled=Aktiv + +management.services.add.property.status.ssoParticipant=Använder SSO +management.services.add.property.status.anonymousAccess=Anonym tillgÃ¥ng +management.services.add.property.attributes=Attribut + +management.services.add.button.save=SPARA +management.services.add.button.cancel=AVBRYT + +management.services.manage.label.name=Webbtjänst +management.services.manage.label.serviceUrl= Webbadress +management.services.manage.label.enabled=Aktiv + +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=ÄNDRA +management.services.manage.action.delete=TA BORT diff --git a/cas-management-webapp/src/main/resources/messages_tr.properties b/cas-management-webapp/src/main/resources/messages_tr.properties new file mode 100644 index 000000000000..6c73f0fce452 --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_tr.properties @@ -0,0 +1,70 @@ +# Author : Mert Caliskan +# http://www.jroller.com/mert + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +#Logout Screen Messages +screen.logout.header=Oturum ba\u015far\u0131yla kapat\u0131ld\u0131. +screen.logout.success=Merkezi Kimliklendirme Servisi'nden ba\u015far\u0131l\u0131 bir \u015fekilde \u00e7\u0131k\u0131\u015f yapt\u0131n\u0131z. + +# SERVICES MANAGEMENT +addServiceView=Yeni Servis Ekle +editServiceView=Servisi G\u00fcncelle +manageServiceView=Servisleri Y\u00f6net + +registeredService.serviceId.exists=Servis URL bilgisine sahip servis bulunmaktad\u0131r. + +application.title=Apereo CAS / Merkezi Kimliklendirme Servisi +application.errors.global=L\u00fctfen a\u015fa\u011f\u0131daki hatalar\u0131 d\u00fczeltin: + +management.services.title=Servis Y\u00f6netimi +management.services.link.logout=\u00c7\u0131k\u0131\u015f + +management.services.status.notdeleted=Servis silinemedi. +management.services.status.deleted={0} ba\u015far\u0131yla silindi. + +management.services.add.instructions=L\u00fctfen sayfan\u0131n alt\u0131nda bulunan, De\u011fi\u015fiklikleri Kaydet d\u00fc\u011fmesine t\u0131klayarak de\u011fi\u015fikliklerinizin kaydedildi\u011fine emin olunuz. +management.services.add.property.name=\u0130sim +management.services.add.property.serviceUrl=Servis URL +management.services.add.property.serviceUrl.instructions=Ant stili \u00f6r\u00fcnt\u00fc e\u015fle\u015ftirmesi kullanabilirsiniz +management.services.add.property.description=Tan\u0131m +management.services.add.property.themeName=Tema \u0130smi +management.services.add.property.status=Durum +management.services.add.property.status.enabled=Aktif + +management.services.add.property.status.ssoParticipant=SSO i\u015ftirak\u00e7isi +management.services.add.property.status.anonymousAccess=Anonim Eri\u015fim +management.services.add.property.attributes=\u00d6zellikler +management.services.add.property.ignoreAttributes=\u00d6zellik Y\u00f6netimi'ni bu ara\u00e7 ile yoksay +management.services.add.property.evaluationOrder=D\u00fczen + +management.services.add.button.save=De\u011fi\u015fiklikleri Kaydet +management.services.add.button.cancel=\u0130ptal + +management.services.manage.label.name=Servis \u0130smi +management.services.manage.label.serviceUrl= Servis URL +management.services.manage.label.enabled=Aktif + +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=g\u00fcncelle +management.services.manage.action.delete=sil diff --git a/cas-management-webapp/src/main/resources/messages_ur.properties b/cas-management-webapp/src/main/resources/messages_ur.properties new file mode 100644 index 000000000000..8cc71ae6f042 --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_ur.properties @@ -0,0 +1,28 @@ +#Author: Faizan Ahmed (Rutgers University) +#Version $Revision$ $Date$ +#Since 3.0.5 + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +#Logout Screen Messages +screen.logout.header=Logout Kamyab +screen.logout.success=Aap kamyabi say Centeral Authentication Service say logout hoo chokay hain. diff --git a/cas-management-webapp/src/main/resources/messages_zh_CN.properties b/cas-management-webapp/src/main/resources/messages_zh_CN.properties new file mode 100644 index 000000000000..0d1acd67e4b6 --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_zh_CN.properties @@ -0,0 +1,72 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Blocked Errors Page +screen.blocked.header=\u8bbf\u95ee\u88ab\u62d2\u7edd + +#Logout Screen Messages +screen.logout.header=\u6ce8\u9500\u6210\u529f +screen.logout.success=\u60a8\u5df2\u7ecf\u6210\u529f\u9000\u51faCAS\u7cfb\u7edf\uff0c\u8c22\u8c22\u4f7f\u7528\uff01 + +# SERVICES MANAGEMENT +addServiceView=\u6dfb\u52a0\u670d\u52a1 +editServiceView=\u7f16\u8f91\u670d\u52a1 +manageServiceView=\u7ba1\u7406\u670d\u52a1 + +registeredService.serviceId.exists=\u5177\u6709\u540c\u4e00\u670d\u52a1URL\u7684\u670d\u52a1\u5df2\u7ecf\u5b58\u5728\u3002 + +application.title=Apereo\u4e2d\u592e\u8ba4\u8bc1\u670d\u52a1 +application.errors.global=\u8bf7\u5148\u7ea0\u6b63\u5982\u4e0b\u9519\u8bef\uff1a + +management.services.title=\u670d\u52a1\u7ba1\u7406 +management.services.link.logout=\u6ce8\u9500 + +management.services.status.notdeleted=\u4e0d\u80fd\u591f\u5220\u9664\u8fd9\u4e00\u670d\u52a1\u3002 +management.services.status.deleted={0}\u670d\u52a1\u5df2\u7ecf\u88ab\u6210\u529f\u5220\u9664\uff01 + +management.services.add.instructions=\u8bf7\u52a1\u5fc5\u5355\u51fb\u9875\u9762\u6700\u5e95\u7aef\u63d0\u4f9b\u7684\u201c\u4fdd\u5b58\u201d\u6309\u94ae\uff0c\u5426\u5219\u53d8\u66f4\u4e0d\u4f1a\u88ab\u751f\u6548 +management.services.add.property.name=\u540d\u5b57 +management.services.add.property.serviceUrl=\u670d\u52a1URL +management.services.add.property.serviceUrl.instructions=\u60a8\u53ef\u4ee5\u4f7f\u7528Ant\u98ce\u683c\u7684\u6a21\u5f0f\u5339\u914d +management.services.add.property.description=\u63cf\u8ff0 +management.services.add.property.themeName=\u4e3b\u9898\u540d +management.services.add.property.status=\u72b6\u6001 +management.services.add.property.status.enabled=\u542f\u7528 + +management.services.add.property.status.ssoParticipant=\u53c2\u4e0eSSO +management.services.add.property.status.anonymousAccess=\u533f\u540d\u8bbf\u95ee +management.services.add.property.attributes=\u5c5e\u6027 +management.services.add.property.ignoreAttributes=\u5ffd\u7565\u6b64\u5de5\u5177\u8bbe\u7f6e\u7684\u5c5e\u6027 +management.services.add.property.evaluationOrder=\u6b21\u5e8f + +management.services.add.button.save=\u4fdd\u5b58 +management.services.add.button.cancel=\u53d6\u6d88 + +management.services.manage.label.name=\u670d\u52a1\u540d +management.services.manage.label.serviceUrl= \u670d\u52a1URL +management.services.manage.label.enabled=\u542f\u7528 + +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=\u7f16\u8f91 +management.services.manage.action.delete=\u5220\u9664 + + diff --git a/cas-management-webapp/src/main/resources/messages_zh_TW.properties b/cas-management-webapp/src/main/resources/messages_zh_TW.properties new file mode 100644 index 000000000000..6eba7fbe1569 --- /dev/null +++ b/cas-management-webapp/src/main/resources/messages_zh_TW.properties @@ -0,0 +1,72 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Blocked Errors Page +screen.blocked.header=\u8a2a\u554f\u88ab\u62d2\u7d55 + +#Logout Screen Messages +screen.logout.header=\u8a3b\u92b7\u6210\u529f +screen.logout.success=\u60a8\u5df2\u7d93\u6210\u529f\u9000\u51faCAS\u7cfb\u7d71\uff0c\u8b1d\u8b1d\u4f7f\u7528\uff01 + +# SERVICES MANAGEMENT +addServiceView=\u6dfb\u52a0\u670d\u52d9 +editServiceView=\u7de8\u8f2f\u670d\u52d9 +manageServiceView=\u7ba1\u7406\u670d\u52d9 + +registeredService.serviceId.exists=\u5177\u6709\u540c\u4e00\u670d\u52d9URL\u7684\u670d\u52d9\u5df2\u7d93\u5b58\u5728\u3002 + +application.title=Apereo\u4e2d\u592e\u8a8d\u8b49\u670d\u52d9 +application.errors.global=\u8acb\u5148\u7cfe\u6b63\u5982\u4e0b\u932f\u8aa4\uff1a + +management.services.title=\u670d\u52d9\u7ba1\u7406 +management.services.link.logout=\u8a3b\u92b7 + +management.services.status.notdeleted=\u4e0d\u80fd\u5920\u522a\u9664\u9019\u4e00\u670d\u52d9\u3002 +management.services.status.deleted={0}\u670d\u52d9\u5df2\u7d93\u88ab\u6210\u529f\u522a\u9664\uff01 + +management.services.add.instructions=\u8acb\u52d9\u5fc5\u55ae\u64ca\u9801\u9762\u6700\u5e95\u7aef\u63d0\u4f9b\u7684\u201c\u4fdd\u5b58\u201d\u6309\u9215\uff0c\u5426\u5247\u8b8a\u66f4\u4e0d\u6703\u88ab\u751f\u6548 +management.services.add.property.name=\u540d\u5b57 +management.services.add.property.serviceUrl=\u670d\u52d9URL +management.services.add.property.serviceUrl.instructions=\u60a8\u53ef\u4ee5\u4f7f\u7528Ant\u98a8\u683c\u7684\u6a21\u5f0f\u5339\u914d +management.services.add.property.description=\u63cf\u8ff0 +management.services.add.property.themeName=\u4e3b\u984c\u540d +management.services.add.property.status=\u72c0\u614b +management.services.add.property.status.enabled=\u555f\u7528 + +management.services.add.property.status.ssoParticipant=\u53c3\u8207SSO +management.services.add.property.status.anonymousAccess=\u533f\u540d\u8a2a\u554f +management.services.add.property.attributes=\u5c6c\u6027 +management.services.add.property.ignoreAttributes=\u5ffd\u7565\u6b64\u5de5\u5177\u8a2d\u7f6e\u7684\u5c6c\u6027 +management.services.add.property.evaluationOrder=\u6b21\u5e8f + +management.services.add.button.save=\u4fdd\u5b58 +management.services.add.button.cancel=\u53d6\u6d88 + +management.services.manage.label.name=\u670d\u52d9\u540d +management.services.manage.label.serviceUrl= \u670d\u52d9URL +management.services.manage.label.enabled=\u555f\u7528 + +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=\u7de8\u8f2f +management.services.manage.action.delete=\u522a\u9664 + + diff --git a/cas-management-webapp/src/main/resources/services/apereo.json b/cas-management-webapp/src/main/resources/services/apereo.json new file mode 100644 index 000000000000..3607f64c3a6b --- /dev/null +++ b/cas-management-webapp/src/main/resources/services/apereo.json @@ -0,0 +1,9 @@ +{ + "@class" : "org.jasig.cas.services.RegexRegisteredService", + "id" : 0, + "description" : "Apereo foundation sample service", + "serviceId" : "^https://www.apereo.org", + "name" : "Apereo", + "theme" : "apereo", + "evaluationOrder" : 0 +} diff --git a/cas-management-webapp/src/main/resources/services/https-imaps.json b/cas-management-webapp/src/main/resources/services/https-imaps.json new file mode 100644 index 000000000000..01a760c4fef9 --- /dev/null +++ b/cas-management-webapp/src/main/resources/services/https-imaps.json @@ -0,0 +1,11 @@ +{ + "@class" : "org.jasig.cas.services.RegexRegisteredService", + "id" : 10000001, + "description" : "This service definition authorized all application urls that support HTTPS and IMAPS protocols.", + "serviceId" : "^(https|imaps)://.*", + "name": "HTTPS and IMAPS", + "evaluationOrder" : 10000001, + "logo" : null, + "logoutType" : "BACK_CHANNEL", + "requiredHandlers" : [ "java.util.HashSet", [ ] ] +} diff --git a/cas-management-webapp/src/main/resources/user-details.properties b/cas-management-webapp/src/main/resources/user-details.properties new file mode 100644 index 000000000000..24a4cf321abf --- /dev/null +++ b/cas-management-webapp/src/main/resources/user-details.properties @@ -0,0 +1,29 @@ + # +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# This file lists the set of users that are allowed access to the management app. +# See this link for more info: +# http://docs.spring.io/spring-security/site/docs/3.0.x/reference/ns-config.html +# +# The syntax of each entry should be in the form of: +# +# username=password,grantedAuthority[,grantedAuthority][,enabled|disabled] + +# Example: +# casuser=notused,ROLE_ADMIN diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/cas-management-servlet.xml b/cas-management-webapp/src/main/webapp/WEB-INF/cas-management-servlet.xml new file mode 100644 index 000000000000..fbd5b4b25c39 --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/cas-management-servlet.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + ${cas-management.viewResolver.basename} + + + + + + + + + + + + + + + + + + + passThroughController + + + + + + + + + + diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/cas-management.properties b/cas-management-webapp/src/main/webapp/WEB-INF/cas-management.properties new file mode 100644 index 000000000000..1aa47e941582 --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/cas-management.properties @@ -0,0 +1,69 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# hosts and urls +# CAS +cas.host=http://localhost:8080 +cas.prefix=${cas.host}/cas +cas.securityContext.casProcessingFilterEntryPoint.loginUrl=${cas.prefix}/login +cas.securityContext.ticketValidator.casServerUrlPrefix=${cas.prefix} +# Management +cas-management.host=${cas.host} +cas-management.prefix=${cas-management.host}/cas-management +cas-management.securityContext.serviceProperties.service=${cas-management.prefix}/j_spring_cas_security_check +cas-management.securityContext.serviceProperties.adminRoles=ROLE_ADMIN + +# views +cas-management.viewResolver.basename=default_views + +## +# User details file location that contains list of users +# who are allowed access to the management webapp: +# +# user.details.file.location = classpath:user-details.properties + +## +# JSON Service Registry +# +# Directory location where JSON service files may be found. +# service.registry.config.location=classpath:services + +## +# Database flavors for Hibernate +# +# One of these is needed if you are storing Services in an RDBMS via JPA. +# +# database.hibernate.dialect=org.hibernate.dialect.OracleDialect +# database.hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect +# database.hibernate.dialect=org.hibernate.dialect.HSQLDialect + +## +# Log4j +# Default sourced from WEB-INF/spring-configuration/log4jConfiguration.xml: +# +# It is often time helpful to externalize log4j.xml to a system path to preserve settings between upgrades. +# e.g. log4j.config.location=/etc/cas/log4j2.xml +# log4j.config.location=classpath:log4j2.xml + +## +# Metrics +# Default sourced from WEB-INF/spring-configuration/metricsConfiguration.xml: +# +# Define how often should metric data be reported. Default is 30 seconds. +# metrics.refresh.internal=30s diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/managementConfigContext.xml b/cas-management-webapp/src/main/webapp/WEB-INF/managementConfigContext.xml new file mode 100644 index 000000000000..b38654680ec6 --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/managementConfigContext.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/README.txt b/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/README.txt similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/README.txt rename to cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/README.txt diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/applicationContext.xml b/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/applicationContext.xml new file mode 100644 index 000000000000..0b516be155f6 --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/applicationContext.xml @@ -0,0 +1,74 @@ + + + + + This is the main Spring configuration file with some of the main "core" classes defined. You shouldn't really + modify this unless you know what you're doing! + + + + + + + + classpath:custom_messages + classpath:messages + + + + + + + + + + + + + + + + + + + diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/auditTrailContext.xml b/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/auditTrailContext.xml new file mode 100644 index 000000000000..21ff759c1f0a --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/auditTrailContext.xml @@ -0,0 +1,75 @@ + + + + + + Configuration file for the Inspektr package which handles auditing for Java applications. + If enabled this should be modified to log audit and statistics information the same way + your local applications do. The default is currently to log to the console which is good + for debugging/testing purposes. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/filters.xml b/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/filters.xml new file mode 100644 index 000000000000..a0fb21fa697a --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/filters.xml @@ -0,0 +1,30 @@ + + + + + + \ No newline at end of file diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/log4jConfiguration.xml b/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/log4jConfiguration.xml new file mode 100644 index 000000000000..d80a3bfa7760 --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/log4jConfiguration.xml @@ -0,0 +1,43 @@ + + + + + + Log4J initialization. Configuration options are sourced from cas.properties. This allows deployers to externalize + both cas.properties and log4j.xml, so that a single cas.war file can be deployed to multiple tiers or hosts without + having to do any post configuration. This approach helps to preserve configuration between upgrades. + + Deployers should not have to edit this file. + + + + + diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/propertyFileConfigurer.xml b/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/propertyFileConfigurer.xml new file mode 100644 index 000000000000..5ebe1b5d4075 --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/propertyFileConfigurer.xml @@ -0,0 +1,38 @@ + + + + + This file lets CAS know where you've stored the cas.properties file which details some of the configuration + options + that are specific to your environment. You can specify the location of the file here. You may wish to place the + file outside + of the Servlet context if you have options that are specific to a tier (i.e. test vs. production) so that the + WAR file + can be moved between tiers without modification. + + + + diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/securityContext.xml b/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/securityContext.xml new file mode 100644 index 000000000000..8a61cd701f7b --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/spring-configuration/securityContext.xml @@ -0,0 +1,85 @@ + + + + + + Security configuration for services management and other sensitive areas of CAS. + In most cases it should not be necessary to edit this file as common configuration + can be managed by setting properties in the cas.properties file. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/add.jsp b/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/add.jsp new file mode 100644 index 000000000000..74599de5706d --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/add.jsp @@ -0,0 +1,118 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@include file="includes/top.jsp" %> +

+ + + +
${successMessage}
+
+ + +
+ +
+
+
+ +
+

+ + + + +
+
+ + + + + +
+
+
+ + + + + + +
+
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+
+ +
+
+
+ + or +
+
+<%@include file="includes/bottom.jsp" %> diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/authorizationFailure.jsp b/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/authorizationFailure.jsp new file mode 100644 index 000000000000..70f79f9f412e --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/authorizationFailure.jsp @@ -0,0 +1,31 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + + +<%@ page isErrorPage="true" %> + +

+

+

+

+
+ + diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/errors.jsp b/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/errors.jsp new file mode 100644 index 000000000000..a5cb4ee86976 --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/errors.jsp @@ -0,0 +1,29 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + +

+ +

+

+ +

+
+ diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/includes/bottom.jsp b/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/includes/bottom.jsp new file mode 100644 index 000000000000..9ef06cad9a75 --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/includes/bottom.jsp @@ -0,0 +1,43 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + + + + + + + + diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/includes/top.jsp b/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/includes/top.jsp new file mode 100644 index 000000000000..c1ee7186cd37 --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/includes/top.jsp @@ -0,0 +1,74 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + +<%@ page language="java" session="false" %> +<%@ page pageEncoding="UTF-8" %> +<%@ page contentType="text/html; charset=UTF-8" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + <spring:message code="management.services.title"/> + + " type="image/x-icon"/> + " type="text/css"/> + + + + + + + + + + + + +
diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/logout.jsp b/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/logout.jsp new file mode 100644 index 000000000000..8ebcd1a9cc90 --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/logout.jsp @@ -0,0 +1,29 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + +

+ +

+

+ +

+
+ diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/manage.jsp b/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/manage.jsp new file mode 100644 index 000000000000..a708781825b2 --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/view/jsp/manage.jsp @@ -0,0 +1,83 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@include file="includes/top.jsp" %> + +

+ +

+
+ + +
+
+ +
+ +
+ + + + + + + + + + + + + + +
  
+ +
+ + + + + + + + + + + + + + + + + +
${service.name}${fn:length(service.serviceId) < 100 ? service.serviceId : fn:substring(service.serviceId, 0, 100)}${service.enabled ? 'Enabled' : 'Disabled'}${service.ssoEnabled ? 'SSO Enabled' : 'SSO Disabled'}${service.anonymousAccess ? 'Anonyous Access Enabled' : 'Anonyous Access Disabled'} + ${service.usernameAttribute}${service.evaluationOrder}
+
+
+<%@include file="includes/bottom.jsp" %> diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/view/views.xml b/cas-management-webapp/src/main/webapp/WEB-INF/view/views.xml new file mode 100644 index 000000000000..764e65b2f8f2 --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/view/views.xml @@ -0,0 +1,27 @@ + + + + + + diff --git a/cas-management-webapp/src/main/webapp/WEB-INF/web.xml b/cas-management-webapp/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000000..be146448dade --- /dev/null +++ b/cas-management-webapp/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,122 @@ + + + + CAS Management 4.1.0-SNAPSHOT + + + contextConfigLocation + + /WEB-INF/spring-configuration/*.xml + /WEB-INF/managementConfigContext.xml + + + + + CAS Client Info Logging Filter + org.jasig.inspektr.common.web.ClientInfoThreadLocalFilter + + + CAS Client Info Logging Filter + /* + + + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + springSecurityFilterChain + /* + + + + characterEncodingFilter + org.springframework.web.filter.DelegatingFilterProxy + + + characterEncodingFilter + /* + + + + + org.springframework.web.context.ContextLoaderListener + + + + cas-management + org.springframework.web.servlet.DispatcherServlet + + contextConfigLocation + /WEB-INF/cas-management-servlet.xml, /WEB-INF/cas-management-servlet-*.xml + + + publishContext + false + + 1 + + + + cas-management + *.html + + + + + 5 + + + + 401 + /authorizationFailure.html + + + + 404 + / + + + + 500 + /WEB-INF/view/jsp/errors.jsp + + + + 501 + /WEB-INF/view/jsp/errors.jsp + + + + 503 + /WEB-INF/view/jsp/errors.jsp + + + + index.jsp + + diff --git a/cas-management-webapp/src/main/webapp/css/management.css b/cas-management-webapp/src/main/webapp/css/management.css new file mode 100644 index 000000000000..3da3b71e657d --- /dev/null +++ b/cas-management-webapp/src/main/webapp/css/management.css @@ -0,0 +1,670 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* $Id$ */ +/* GLOBAL/UTILITIES + --------------------------------- */ +/* reset margins and padding for all elements since defaults are not crossbrowser */ + +/* + * Licensed to Jasig under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Jasig licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +* { + margin: 0; + padding: 0; + font-size: 1em; +} + +/* browser default font-size is 16px which is too big so we make it 16px x 62.5% = 10px */ +body { + font: normal 62.5%/1 Verdana, Arial, Helvetica, sans-serif; + min-width: 992px; +} + +form { + float: left; + margin: 0 1%; + padding: 0; + width: 77%; +} + +/* general positioning styles */ +.ac { + text-align: center !important; +} + +/* HEADER + --------------------------------- */ +#header #nav-system { + float: right; + padding: 0; +} + +#header #nav-system ul { + list-style: none; + margin: 0; + padding: 0; +} + +#header #nav-system li { + float: left; + margin: 0; + padding: 0; +} + +#header #nav-system li a { + float: left; + margin: 0 0 0 1px; + padding: 2px 10px; + font: normal 1.1em/1.5 Verdana, Arial, Helvetica, sans-serif; + text-decoration: none; + background-color: #323265; + color: #fff; + white-space: nowrap; +} + +#header #nav-system li a:hover, +#header #nav-system li a:focus { + background-color: #fff; + color: #323265; +} + +#header p#tagline { + padding: 0 0 2px 3px; + background: #323265; + color: #fff; + font-size: 1.2em; + line-height: 1.6; +} + +#header h1#app-name { + clear: both; + padding: 0 0 0 15px; + background: #323265; + color: #fff; + font: 2.4em/2em Arial, Helvetica, sans-serif; +} + +/* MAIN MENU + --------------------------------- */ +#nav-main { + float: left; + width: 100%; + background: #999; + font-size: 1.1em; + line-height: normal; + padding: 0; + xmargin: 0 0 1.5em 0; + color: #eee; +} + +#nav-main ul { + margin: 0; + padding: 0; + list-style: none; + line-height: 1.4em; +} + +#nav-main li { + display: inline; + margin: 0; + padding: 0; +} + +#nav-main a { + float: left; + margin: 0; + padding: 0; + text-decoration: none; +} + +#nav-main a span { + float: left; + display: block; + padding: 5px 10px; + background: transparent; + color: #eee; +} + +/* Hide from IE5Mac only \*/ +#nav-main a span { + float: none; +} + +/* End hack */ +#nav-main a:hover { + background: #eee; +} + +#nav-main a:hover span { + background: #eee; + color: #333; +} + +.highlightBottom td { + background: #FFEFF3; + color: #666; + font-weight: 400; +} + +.highlightBottom td a { + display: inline; + background: #fff; + padding: 5px 10px; + color: #666; + font-weight: normal; +} + +.highlightBottom a:hover { + background: #b00; + color: #fff; +} + +/* CONTENT + --------------------------------- */ +#content { + clear: both; + width: auto; + padding: 1px 0; + margin: 0 2% 2em; +} + +#content h1 { + margin: 15px 0; + font: normal 2.2em "Times New Roman", serif; + color: #333; + background: transparent; + text-transform: capitalize; +} + +#content p { + margin: 1em 0; +} + +/* FOOTER +---------------------------------------------------------- */ +#footer { + color: #999; + background: transparent; + clear: both; + margin: 0 2% 2em; + padding: 0 0 1px 0; + border-top: 1px solid #ccc; + position: relative; +} + +#footer div { + margin: 1em 5px .5em; + clear: left; + overflow: hidden; +} + +#footer h4 { + font: normal 1em/1.2 Verdana, Arial, Helvetica, sans-serif; + clear: left; + margin: 0; + padding: 0; + float: left; +} + +#footer a#jasig { + position: relative; + float: right; + clear: both; + display: block; + background-image: url(../images/ja-sig-logo.gif); + width: 118px; + height: 31px; + margin: 1em 5px .5em; +} + +#footer #nav-campus-sites { + list-style: none; + float: left; + margin: 0 0 0 5px; + padding: 0; +} + +#footer #nav-campus-sites li { + display: inline; + padding: 0; + margin: 0; + font: normal 1em/1.2 Verdana, Arial, Helvetica, sans-serif; +} + +#footer #nav-campus-sites li:before { +} + +/* content: " | " */ +#footer #nav-campus-sites li:first-child:before { +} + +/* All IE browsers */ +* html #footer { + height: 1px; +} + +* html #footer #nav-campus-sites { + padding: 0 0.4em 0 0; + margin: 0; +} + +/* Win IE browsers - hide from Mac IE\*/ +* html #footer #nav-campus-sites { + height: 1px; +} + +* html #footer #nav-campus-sites li { + display: block; + float: left; +} + +/* End hide from Mac IE 5 */ +* html #footer #nav-campus-sites li:first-child { + border-left: 0px none; +} + +/* +_______________________________ +--- CONTENT FRAGMENTS --- +_______________________________ +*/ +/* MESSAGES + --------------------------------- */ +.errors, .success { + clear: both; + padding: 20px 20px 20px 85px; /* bg */ + margin: 0 0 1em; + font-weight: bold; + font-size: 1.3em; + line-height: 1.5; +} + +.success { + border: 1px dotted #390; + color: #390; + background: #dfa url('../images/success.gif') no-repeat 2em 50%; +} + +.errors { + border: 1px dotted #b00; + color: #e71708; /* bg */ + background: #fed url('../images/error.gif') no-repeat 2em 50%; +} + +/* FORMS + --------------------------------- */ +fieldset { + border-left: 0px solid #ddd; + border-right: 0px solid #ddd; + border-top: 1px solid #ddd; + border-bottom: 0px solid #ddd; + margin: 2em 0; + padding: 10px; +} + +legend { + margin: 1em 5px; + color: #b00; + font-size: 1.1em; + font-weight: bold; + text-transform: uppercase; +} + +label { + cursor: pointer; + font-size: 1em; + color: #666; +} + +input, select, textarea, option { + font: normal 1.1em sans-serif; +} + +input, textarea { + padding: 0px 2px; +} + +select option { + margin: auto .5em 0 0; +} + +/* vertically aligned form*/ +input.check { + margin: 0 0 0 .5em; + width: 13px; + height: 13px; + vertical-align: middle; +} + +/* highlight errors */ +.required { + background: #ff9; +} + +.formError { + background: #fafafa url('../images/alert2.gif') no-repeat 0 50%; + /* bg */ + color: #b00; /* bg */ + margin-left: .5em; /* bg */ + font-size: 1.4em; + line-height: 20px; + padding-left: 24px; + padding-right: 0; + padding-top: 0; + padding-bottom: 0 +} + +/* TABLES + --------------------------------- */ +/* table row highlighting (does not work in IE - JavaScript workaround) */ +table.highlight tr:hover td, table.highlight tr.over td { + background: #ffc !important; +} + +table.highlight tr.highlightBottom:hover td { + background: #ffeff3 !important; +} + +table { + border: 0px none; + border-collapse: collapse; + empty-cells: show; + background-color: #fff; + font-size: 1.1em; + border-collapse: separate; + border-spacing: 0px; +} + +.large { + width: 100%; +} + +th { + background: #eee; + color: #666; + padding: 3px 5px; + text-align: left; + font-weight: normal; + line-height: 24px; +} + +tr.added { + background-color: #ff3; +} + +td { + padding: 3px 5px; + border-bottom: 1px solid #eee; + height: 38px; +} + +td a { + padding: 10px 0pt 10px 35px; + text-decoration: none; + display: inline; + line-height: 32px; + color: #c1c1c0; + font-weight: 400; +} + +.add { + min-width: 952px; + line-height: 32px; + height: 32px; + border-top: 5px solid #eee; + color: #000; + width: 100%; + text-indent: 5px; + padding-top: 5px; + font-size: 1.1em; +} + +.add a { + background: url('../images/add_service.gif') no-repeat left center; + text-decoration: none; + display: inline; + line-height: 32px; + color: #c1c1c0; + font-weight: 900; + padding-left: 35px; + padding-right: 0; + padding-top: 10px; + padding-bottom: 10px +} + +.add a:hover { + color: #b00; +} + +td a.edit { + background: url('../images/edit_service.gif') no-repeat left center; +} + +td a.del { + background: url('../images/delete_service.gif') no-repeat left center; +} + +td a:hover { + color: #b00; +} + +p.instructions { + margin: 1em 0; + font-size: 1.2em; + background: url('../images/info.gif') no-repeat left center; + padding-left: 2em; + padding-right: 0; + padding-top: 5px; + padding-bottom: 5px +} + +fieldset { + padding: 0 10px; + background-color: #fafafa; + margin: 0; + position: relative; + border: 1px solid #ddd; +} + +legend { + padding: 2px 5px; + color: #b00; + font-size: 1.3em; + font-weight: 900; + background: #fff; +} + +label.preField, .label { + display: block; + width: 8em; + float: left; + font-size: 1.1em; + color: #666; /* + bg */ + line-height: 20px; /* + bg */ +} + +label.postField { + margin-right: 1em; + font-size: 1.1em; + vertical-align: middle; /*- bg */ + line-height: 20px; /* + bg */ +} + +.check { + border: 0px none; +} + +input, +textarea, +select { + border: 1px solid #ccc; + border-color: #999 #eee #eee #999; + padding: 2px; + margin-left: .5em; /* + bg */ +} + +input { + /* margin-left:.5em; - bg */ + font-size: 1.1em; + vertical-align: top; +} + +span.oneField { + display: block; + margin: 1em 0; /* bg */ + padding: 0; +} + +button { + font-size: 1em; +} + +.primaryAction { + padding: .5em; + color: green; + font-weight: 900; +} + +@media screen { + div#container { + width: 100%; + min-width: 952px; + margin: 0; + padding: 0; + } + + table#headerTable { + width: 100%; + min-width: 952px; + background: #999; + margin: 0; + padding: 0; + border: 0; + border-collapse: collapse; + } + + div.tableWrapper { + width: 100%; + min-width: 952px; + max-height: 250px; + overflow: auto; + overflow-x: hidden; + } + + table#scrollTable { + width: 100%; + min-width: 935px; + } + + table#scrollTable thead { + display: none; + } + + table#headerTable th, table#scrollTable td { + padding: 0 5px; + border: 0; + } + + table#scrollTable td { + text-align: left; + border-bottom: 1px solid #eee; + } + + table#headerTable th { + height: 38px; + border: 0 !important; + } + + th.th1, td.td1 { + width: 200px; + overflow: hidden; + } + + th.th2, td.td2 { + overflow: hidden; + } + + th.th3, td.td3 { + width: 50px; + } + + th.th4, td.td4 { + width: 70px; + } + + th.th5, td.td5 { + width: 50px; + } + + th.th6, td.td6 { + width: 70px; + } + + th.th7, td.td7 { + width: 102px; + } + + th.th8, td.td8 { + width: 70px + } + + th.th9, td.td9 { + width: 70px; + text-align: right !important; + } + + th.th10, td.td10 { + width: 102px; + text-align: right !important; + } + + .hint { + margin-left: 9.5em; + margin-bottom: 1em; + line-height: 1.5; + } + + .actions { + margin: 1.5em 0; + } +} + +/* Container that holds the error messages on the services manage page + * This will be hidden by default and through JS calls, will be displayed + * when appropriate. + */ +#errorsDiv { + display: none; +} diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/favicon.ico b/cas-management-webapp/src/main/webapp/favicon.ico similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/favicon.ico rename to cas-management-webapp/src/main/webapp/favicon.ico diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/add_service.gif b/cas-management-webapp/src/main/webapp/images/add_service.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/add_service.gif rename to cas-management-webapp/src/main/webapp/images/add_service.gif diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/alert2.gif b/cas-management-webapp/src/main/webapp/images/alert2.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/alert2.gif rename to cas-management-webapp/src/main/webapp/images/alert2.gif diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/delete_service.gif b/cas-management-webapp/src/main/webapp/images/delete_service.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/delete_service.gif rename to cas-management-webapp/src/main/webapp/images/delete_service.gif diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/edit_service.gif b/cas-management-webapp/src/main/webapp/images/edit_service.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/edit_service.gif rename to cas-management-webapp/src/main/webapp/images/edit_service.gif diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/error.gif b/cas-management-webapp/src/main/webapp/images/error.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/error.gif rename to cas-management-webapp/src/main/webapp/images/error.gif diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/false.gif b/cas-management-webapp/src/main/webapp/images/false.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/false.gif rename to cas-management-webapp/src/main/webapp/images/false.gif diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/info.gif b/cas-management-webapp/src/main/webapp/images/info.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/info.gif rename to cas-management-webapp/src/main/webapp/images/info.gif diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/info_icon_small.gif b/cas-management-webapp/src/main/webapp/images/info_icon_small.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/info_icon_small.gif rename to cas-management-webapp/src/main/webapp/images/info_icon_small.gif diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/ja-sig-logo.gif b/cas-management-webapp/src/main/webapp/images/ja-sig-logo.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/ja-sig-logo.gif rename to cas-management-webapp/src/main/webapp/images/ja-sig-logo.gif diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/success.gif b/cas-management-webapp/src/main/webapp/images/success.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/success.gif rename to cas-management-webapp/src/main/webapp/images/success.gif diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/true.gif b/cas-management-webapp/src/main/webapp/images/true.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/services/true.gif rename to cas-management-webapp/src/main/webapp/images/true.gif diff --git a/cas-management-webapp/src/main/webapp/index.jsp b/cas-management-webapp/src/main/webapp/index.jsp new file mode 100644 index 000000000000..c92b3cacbcd4 --- /dev/null +++ b/cas-management-webapp/src/main/webapp/index.jsp @@ -0,0 +1,43 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@ page language="java" session="false" %> +<%-- + ~ Licensed to Apereo under one or more contributor license + ~ agreements. See the NOTICE file distributed with this work + ~ for additional information regarding copyright ownership. + ~ Apereo licenses this file to you under the Apache License, + ~ Version 2.0 (the "License"); you may not use this file + ~ except in compliance with the License. You may obtain a + ~ copy of the License at the following location: + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + --%> + +<% +final String url = request.getContextPath() + "/manage.html"; +response.sendRedirect(response.encodeURL(url));%> \ No newline at end of file diff --git a/cas-management-webapp/src/main/webapp/js/MyInfusion.js b/cas-management-webapp/src/main/webapp/js/MyInfusion.js new file mode 100644 index 000000000000..1fda024946cd --- /dev/null +++ b/cas-management-webapp/src/main/webapp/js/MyInfusion.js @@ -0,0 +1,30128 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/*! + * jQuery JavaScript Library v1.6.1 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Thu May 12 15:04:36 2011 -0400 + */ + +(function (window, undefined) { + +// Use the correct document accordingly with window argument (sandbox) + var document = window.document, + navigator = window.navigator, + location = window.location; + var jQuery = (function () { + +// Define a local copy of jQuery + var jQuery = function (selector, context) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init(selector, context, rootjQuery); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Check for digits + rdigit = /\d/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // The deferred used on DOM ready + readyList, + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + + jQuery.fn = jQuery.prototype = { + constructor: jQuery, + init: function (selector, context, rootjQuery) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if (!selector) { + return this; + } + + // Handle $(DOMElement) + if (selector.nodeType) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if (selector === "body" && !context && document.body) { + this.context = document; + this[0] = document.body; + this.selector = selector; + this.length = 1; + return this; + } + + // Handle HTML strings + if (typeof selector === "string") { + // Are we dealing with HTML string or an ID? + if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [null, selector, null]; + + } else { + match = quickExpr.exec(selector); + } + + // Verify a match, and that no context was specified for #id + if (match && (match[1] || !context)) { + + // HANDLE: $(html) -> $(array) + if (match[1]) { + context = context instanceof jQuery ? context[0] : context; + doc = (context ? context.ownerDocument || context : document); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec(selector); + + if (ret) { + if (jQuery.isPlainObject(context)) { + selector = [document.createElement(ret[1])]; + jQuery.fn.attr.call(selector, context, true); + + } else { + selector = [doc.createElement(ret[1])]; + } + + } else { + ret = jQuery.buildFragment([match[1]], [doc]); + selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes; + } + + return jQuery.merge(this, selector); + + // HANDLE: $("#id") + } else { + elem = document.getElementById(match[2]); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if (elem && elem.parentNode) { + // Handle the case where IE and Opera return items + // by name instead of ID + if (elem.id !== match[2]) { + return rootjQuery.find(selector); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if (!context || context.jquery) { + return (context || rootjQuery).find(selector); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor(context).find(selector); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if (jQuery.isFunction(selector)) { + return rootjQuery.ready(selector); + } + + if (selector.selector !== undefined) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray(selector, this); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.6.1", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function () { + return this.length; + }, + + toArray: function () { + return slice.call(this, 0); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function (num) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[this.length + num] : this[num] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function (elems, name, selector) { + // Build a new jQuery matched element set + var ret = this.constructor(); + + if (jQuery.isArray(elems)) { + push.apply(ret, elems); + + } else { + jQuery.merge(ret, elems); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if (name === "find") { + ret.selector = this.selector + (this.selector ? " " : "") + selector; + } else if (name) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function (callback, args) { + return jQuery.each(this, callback, args); + }, + + ready: function (fn) { + // Attach the listeners + jQuery.bindReady(); + + // Add the callback + readyList.done(fn); + + return this; + }, + + eq: function (i) { + return i === -1 ? + this.slice(i) : + this.slice(i, +i + 1); + }, + + first: function () { + return this.eq(0); + }, + + last: function () { + return this.eq(-1); + }, + + slice: function () { + return this.pushStack(slice.apply(this, arguments), + "slice", slice.call(arguments).join(",")); + }, + + map: function (callback) { + return this.pushStack(jQuery.map(this, function (elem, i) { + return callback.call(elem, i, elem); + })); + }, + + end: function () { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice + }; + +// Give the init function the jQuery prototype for later instantiation + jQuery.fn.init.prototype = jQuery.fn; + + jQuery.extend = jQuery.fn.extend = function () { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if (typeof target === "boolean") { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if (typeof target !== "object" && !jQuery.isFunction(target)) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if (length === i) { + target = this; + --i; + } + + for (; i < length; i++) { + // Only deal with non-null/undefined values + if ((options = arguments[i]) != null) { + // Extend the base object + for (name in options) { + src = target[name]; + copy = options[name]; + + // Prevent never-ending loop + if (target === copy) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if (deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) )) { + if (copyIsArray) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[name] = jQuery.extend(deep, clone, copy); + + // Don't bring in undefined values + } else if (copy !== undefined) { + target[name] = copy; + } + } + } + } + + // Return the modified object + return target; + }; + + jQuery.extend({ + noConflict: function (deep) { + if (window.$ === jQuery) { + window.$ = _$; + } + + if (deep && window.jQuery === jQuery) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function (hold) { + if (hold) { + jQuery.readyWait++; + } else { + jQuery.ready(true); + } + }, + + // Handle when the DOM is ready + ready: function (wait) { + // Either a released hold or an DOMready/load event and not yet ready + if ((wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady)) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if (!document.body) { + return setTimeout(jQuery.ready, 1); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if (wait !== true && --jQuery.readyWait > 0) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith(document, [jQuery]); + + // Trigger any bound ready events + if (jQuery.fn.trigger) { + jQuery(document).trigger("ready").unbind("ready"); + } + } + }, + + bindReady: function () { + if (readyList) { + return; + } + + readyList = jQuery._Deferred(); + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if (document.readyState === "complete") { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout(jQuery.ready, 1); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if (document.addEventListener) { + // Use the handy event callback + document.addEventListener("DOMContentLoaded", DOMContentLoaded, false); + + // A fallback to window.onload, that will always work + window.addEventListener("load", jQuery.ready, false); + + // If IE event model is used + } else if (document.attachEvent) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent("onreadystatechange", DOMContentLoaded); + + // A fallback to window.onload, that will always work + window.attachEvent("onload", jQuery.ready); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch (e) { + } + + if (document.documentElement.doScroll && toplevel) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function (obj) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function (obj) { + return jQuery.type(obj) === "array"; + }, + + // A crude way of determining if an object is a window + isWindow: function (obj) { + return obj && typeof obj === "object" && "setInterval" in obj; + }, + + isNaN: function (obj) { + return obj == null || !rdigit.test(obj) || isNaN(obj); + }, + + type: function (obj) { + return obj == null ? + String(obj) : + class2type[toString.call(obj)] || "object"; + }, + + isPlainObject: function (obj) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if (!obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow(obj)) { + return false; + } + + // Not own constructor property must be Object + if (obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for (key in obj) { + } + + return key === undefined || hasOwn.call(obj, key); + }, + + isEmptyObject: function (obj) { + for (var name in obj) { + return false; + } + return true; + }, + + error: function (msg) { + throw msg; + }, + + parseJSON: function (data) { + if (typeof data !== "string" || !data) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim(data); + + // Attempt to parse using the native JSON parser first + if (window.JSON && window.JSON.parse) { + return window.JSON.parse(data); + } + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if (rvalidchars.test(data.replace(rvalidescape, "@") + .replace(rvalidtokens, "]") + .replace(rvalidbraces, ""))) { + + return (new Function("return " + data))(); + + } + jQuery.error("Invalid JSON: " + data); + }, + + // Cross-browser xml parsing + // (xml & tmp used internally) + parseXML: function (data, xml, tmp) { + + if (window.DOMParser) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString(data, "text/xml"); + } else { // IE + xml = new ActiveXObject("Microsoft.XMLDOM"); + xml.async = "false"; + xml.loadXML(data); + } + + tmp = xml.documentElement; + + if (!tmp || !tmp.nodeName || tmp.nodeName === "parsererror") { + jQuery.error("Invalid XML: " + data); + } + + return xml; + }, + + noop: function () { + }, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function (data) { + if (data && rnotwhite.test(data)) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function (data) { + window["eval"].call(window, data); + } )(data); + } + }, + + nodeName: function (elem, name) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function (object, callback, args) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction(object); + + if (args) { + if (isObj) { + for (name in object) { + if (callback.apply(object[name], args) === false) { + break; + } + } + } else { + for (; i < length;) { + if (callback.apply(object[i++], args) === false) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if (isObj) { + for (name in object) { + if (callback.call(object[name], name, object[name]) === false) { + break; + } + } + } else { + for (; i < length;) { + if (callback.call(object[i], i, object[i++]) === false) { + break; + } + } + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function (text) { + return text == null ? + "" : + trim.call(text); + } : + + // Otherwise use our own trimming functionality + function (text) { + return text == null ? + "" : + text.toString().replace(trimLeft, "").replace(trimRight, ""); + }, + + // results is for internal usage only + makeArray: function (array, results) { + var ret = results || []; + + if (array != null) { + // The window, strings (and functions) also have 'length' + // The extra typeof function check is to prevent crashes + // in Safari 2 (See: #3039) + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type(array); + + if (array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow(array)) { + push.call(ret, array); + } else { + jQuery.merge(ret, array); + } + } + + return ret; + }, + + inArray: function (elem, array) { + + if (indexOf) { + return indexOf.call(array, elem); + } + + for (var i = 0, length = array.length; i < length; i++) { + if (array[i] === elem) { + return i; + } + } + + return -1; + }, + + merge: function (first, second) { + var i = first.length, + j = 0; + + if (typeof second.length === "number") { + for (var l = second.length; j < l; j++) { + first[i++] = second[j]; + } + + } else { + while (second[j] !== undefined) { + first[i++] = second[j++]; + } + } + + first.length = i; + + return first; + }, + + grep: function (elems, callback, inv) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for (var i = 0, length = elems.length; i < length; i++) { + retVal = !!callback(elems[i], i); + if (inv !== retVal) { + ret.push(elems[i]); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function (elems, callback, arg) { + var value, key, ret = [], + i = 0, + length = elems.length, + // jquery objects are treated as arrays + isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[0] && elems[length - 1] ) || length === 0 || jQuery.isArray(elems) ); + + // Go through the array, translating each of the items to their + if (isArray) { + for (; i < length; i++) { + value = callback(elems[i], i, arg); + + if (value != null) { + ret[ret.length] = value; + } + } + + // Go through every key on the object, + } else { + for (key in elems) { + value = callback(elems[key], key, arg); + + if (value != null) { + ret[ret.length] = value; + } + } + } + + // Flatten any nested arrays + return ret.concat.apply([], ret); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function (fn, context) { + if (typeof context === "string") { + var tmp = fn[context]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if (!jQuery.isFunction(fn)) { + return undefined; + } + + // Simulated bind + var args = slice.call(arguments, 2), + proxy = function () { + return fn.apply(context, args.concat(slice.call(arguments))); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can be optionally by executed if its a function + access: function (elems, key, value, exec, fn, pass) { + var length = elems.length; + + // Setting many attributes + if (typeof key === "object") { + for (var k in key) { + jQuery.access(elems, k, key[k], exec, fn, value); + } + return elems; + } + + // Setting one attribute + if (value !== undefined) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for (var i = 0; i < length; i++) { + fn(elems[i], key, exec ? value.call(elems[i], i, fn(elems[i], key)) : value, pass); + } + + return elems; + } + + // Getting an attribute + return length ? fn(elems[0], key) : undefined; + }, + + now: function () { + return (new Date()).getTime(); + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function (ua) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec(ua) || + ropera.exec(ua) || + rmsie.exec(ua) || + ua.indexOf("compatible") < 0 && rmozilla.exec(ua) || + []; + + return {browser: match[1] || "", version: match[2] || "0"}; + }, + + sub: function () { + function jQuerySub(selector, context) { + return new jQuerySub.fn.init(selector, context); + } + + jQuery.extend(true, jQuerySub, this); + jQuerySub.superclass = this; + jQuerySub.fn = jQuerySub.prototype = this(); + jQuerySub.fn.constructor = jQuerySub; + jQuerySub.sub = this.sub; + jQuerySub.fn.init = function init(selector, context) { + if (context && context instanceof jQuery && !(context instanceof jQuerySub)) { + context = jQuerySub(context); + } + + return jQuery.fn.init.call(this, selector, context, rootjQuerySub); + }; + jQuerySub.fn.init.prototype = jQuerySub.fn; + var rootjQuerySub = jQuerySub(document); + return jQuerySub; + }, + + browser: {} + }); + +// Populate the class2type map + jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function (i, name) { + class2type["[object " + name + "]"] = name.toLowerCase(); + }); + + browserMatch = jQuery.uaMatch(userAgent); + if (browserMatch.browser) { + jQuery.browser[browserMatch.browser] = true; + jQuery.browser.version = browserMatch.version; + } + +// Deprecated, use jQuery.browser.webkit instead + if (jQuery.browser.webkit) { + jQuery.browser.safari = true; + } + +// IE doesn't match non-breaking spaces with \s + if (rnotwhite.test("\xA0")) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; + } + +// All jQuery objects should point back to these + rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method + if (document.addEventListener) { + DOMContentLoaded = function () { + document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false); + jQuery.ready(); + }; + + } else if (document.attachEvent) { + DOMContentLoaded = function () { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if (document.readyState === "complete") { + document.detachEvent("onreadystatechange", DOMContentLoaded); + jQuery.ready(); + } + }; + } + +// The DOM ready check for Internet Explorer + function doScrollCheck() { + if (jQuery.isReady) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch (e) { + setTimeout(doScrollCheck, 1); + return; + } + + // and execute any waiting functions + jQuery.ready(); + } + +// Expose jQuery to the global object + return jQuery; + + })(); + + + var // Promise methods + promiseMethods = "done fail isResolved isRejected promise then always pipe".split(" "), + // Static reference to slice + sliceDeferred = [].slice; + + jQuery.extend({ + // Create a simple deferred (one callbacks list) + _Deferred: function () { + var // callbacks list + callbacks = [], + // stored [ context , args ] + fired, + // to avoid firing when already doing so + firing, + // flag to know if the deferred has been cancelled + cancelled, + // the deferred itself + deferred = { + + // done( f1, f2, ...) + done: function () { + if (!cancelled) { + var args = arguments, + i, + length, + elem, + type, + _fired; + if (fired) { + _fired = fired; + fired = 0; + } + for (i = 0, length = args.length; i < length; i++) { + elem = args[i]; + type = jQuery.type(elem); + if (type === "array") { + deferred.done.apply(deferred, elem); + } else if (type === "function") { + callbacks.push(elem); + } + } + if (_fired) { + deferred.resolveWith(_fired[0], _fired[1]); + } + } + return this; + }, + + // resolve with given context and args + resolveWith: function (context, args) { + if (!cancelled && !fired && !firing) { + // make sure args are available (#8421) + args = args || []; + firing = 1; + try { + while (callbacks[0]) { + callbacks.shift().apply(context, args); + } + } + finally { + fired = [context, args]; + firing = 0; + } + } + return this; + }, + + // resolve with this as context and given arguments + resolve: function () { + deferred.resolveWith(this, arguments); + return this; + }, + + // Has this deferred been resolved? + isResolved: function () { + return !!( firing || fired ); + }, + + // Cancel + cancel: function () { + cancelled = 1; + callbacks = []; + return this; + } + }; + + return deferred; + }, + + // Full fledged deferred (two callbacks list) + Deferred: function (func) { + var deferred = jQuery._Deferred(), + failDeferred = jQuery._Deferred(), + promise; + // Add errorDeferred methods, then and promise + jQuery.extend(deferred, { + then: function (doneCallbacks, failCallbacks) { + deferred.done(doneCallbacks).fail(failCallbacks); + return this; + }, + always: function () { + return deferred.done.apply(deferred, arguments).fail.apply(this, arguments); + }, + fail: failDeferred.done, + rejectWith: failDeferred.resolveWith, + reject: failDeferred.resolve, + isRejected: failDeferred.isResolved, + pipe: function (fnDone, fnFail) { + return jQuery.Deferred(function (newDefer) { + jQuery.each({ + done: [fnDone, "resolve"], + fail: [fnFail, "reject"] + }, function (handler, data) { + var fn = data[0], + action = data[1], + returned; + if (jQuery.isFunction(fn)) { + deferred[handler](function () { + returned = fn.apply(this, arguments); + if (returned && jQuery.isFunction(returned.promise)) { + returned.promise().then(newDefer.resolve, newDefer.reject); + } else { + newDefer[action](returned); + } + }); + } else { + deferred[handler](newDefer[action]); + } + }); + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function (obj) { + if (obj == null) { + if (promise) { + return promise; + } + promise = obj = {}; + } + var i = promiseMethods.length; + while (i--) { + obj[promiseMethods[i]] = deferred[promiseMethods[i]]; + } + return obj; + } + }); + // Make sure only one callback list will be used + deferred.done(failDeferred.cancel).fail(deferred.cancel); + // Unexpose cancel + delete deferred.cancel; + // Call given func if any + if (func) { + func.call(deferred, deferred); + } + return deferred; + }, + + // Deferred helper + when: function (firstParam) { + var args = arguments, + i = 0, + length = args.length, + count = length, + deferred = length <= 1 && firstParam && jQuery.isFunction(firstParam.promise) ? + firstParam : + jQuery.Deferred(); + + function resolveFunc(i) { + return function (value) { + args[i] = arguments.length > 1 ? sliceDeferred.call(arguments, 0) : value; + if (!( --count )) { + // Strange bug in FF4: + // Values changed onto the arguments object sometimes end up as undefined values + // outside the $.when method. Cloning the object into a fresh array solves the issue + deferred.resolveWith(deferred, sliceDeferred.call(args, 0)); + } + }; + } + + if (length > 1) { + for (; i < length; i++) { + if (args[i] && jQuery.isFunction(args[i].promise)) { + args[i].promise().then(resolveFunc(i), deferred.reject); + } else { + --count; + } + } + if (!count) { + deferred.resolveWith(deferred, args); + } + } else if (deferred !== firstParam) { + deferred.resolveWith(deferred, length ? [firstParam] : []); + } + return deferred.promise(); + } + }); + + + jQuery.support = (function () { + + var div = document.createElement("div"), + documentElement = document.documentElement, + all, + a, + select, + opt, + input, + marginDiv, + support, + fragment, + body, + bodyStyle, + tds, + events, + eventName, + i, + isSupported; + + // Preliminary tests + div.setAttribute("className", "t"); + div.innerHTML = "
a"; + + all = div.getElementsByTagName("*"); + a = div.getElementsByTagName("a")[0]; + + // Can't get basic test support + if (!all || !all.length || !a) { + return {}; + } + + // First batch of supports tests + select = document.createElement("select"); + opt = select.appendChild(document.createElement("option")); + input = div.getElementsByTagName("input")[0]; + + support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: ( div.firstChild.nodeType === 3 ), + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test(a.getAttribute("style")), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: ( a.getAttribute("href") === "/a" ), + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55$/.test(a.style.opacity), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: ( input.value === "on" ), + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // Will be defined later + submitBubbles: true, + changeBubbles: true, + focusinBubbles: false, + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true + }; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode(true).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + } catch (e) { + support.deleteExpando = false; + } + + if (!div.addEventListener && div.attachEvent && div.fireEvent) { + div.attachEvent("onclick", function click() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + support.noCloneEvent = false; + div.detachEvent("onclick", click); + }); + div.cloneNode(true).fireEvent("onclick"); + } + + // Check if a radio maintains it's value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute("type", "radio"); + support.radioValue = input.value === "t"; + + input.setAttribute("checked", "checked"); + div.appendChild(input); + fragment = document.createDocumentFragment(); + fragment.appendChild(div.firstChild); + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; + + div.innerHTML = ""; + + // Figure out if the W3C box model works as expected + div.style.width = div.style.paddingLeft = "1px"; + + // We use our own, invisible, body + body = document.createElement("body"); + bodyStyle = { + visibility: "hidden", + width: 0, + height: 0, + border: 0, + margin: 0, + // Set background to avoid IE crashes when removing (#9028) + background: "none" + }; + for (i in bodyStyle) { + body.style[i] = bodyStyle[i]; + } + body.appendChild(div); + documentElement.insertBefore(body, documentElement.firstChild); + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + support.boxModel = div.offsetWidth === 2; + + if ("zoom" in div.style) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.style.display = "inline"; + div.style.zoom = 1; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 ); + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = ""; + div.innerHTML = "
"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); + } + + div.innerHTML = "
t
"; + tds = div.getElementsByTagName("td"); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + isSupported = ( tds[0].offsetHeight === 0 ); + + tds[0].style.display = ""; + tds[1].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE < 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[0].offsetHeight === 0 ); + div.innerHTML = ""; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + if (document.defaultView && document.defaultView.getComputedStyle) { + marginDiv = document.createElement("div"); + marginDiv.style.width = "0"; + marginDiv.style.marginRight = "0"; + div.appendChild(marginDiv); + support.reliableMarginRight = + ( parseInt(( document.defaultView.getComputedStyle(marginDiv, null) || {marginRight: 0} ).marginRight, 10) || 0 ) === 0; + } + + // Remove the body element we added + body.innerHTML = ""; + documentElement.removeChild(body); + + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if (div.attachEvent) { + for (i in { + submit: 1, + change: 1, + focusin: 1 + }) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if (!isSupported) { + div.setAttribute(eventName, "return;"); + isSupported = ( typeof div[eventName] === "function" ); + } + support[i + "Bubbles"] = isSupported; + } + } + + return support; + })(); + +// Keep track of boxModel + jQuery.boxModel = jQuery.support.boxModel; + + + var rbrace = /^(?:\{.*\}|\[.*\])$/, + rmultiDash = /([a-z])([A-Z])/g; + + jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace(/\D/g, ""), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function (elem) { + elem = elem.nodeType ? jQuery.cache[elem[jQuery.expando]] : elem[jQuery.expando]; + + return !!elem && !isEmptyDataObject(elem); + }, + + data: function (elem, name, data, pvt /* Internal Use Only */) { + if (!jQuery.acceptData(elem)) { + return; + } + + var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache, + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[jQuery.expando] : elem[jQuery.expando] && jQuery.expando; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ((!id || (pvt && id && !cache[id][internalKey])) && getByName && data === undefined) { + return; + } + + if (!id) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if (isNode) { + elem[jQuery.expando] = id = ++jQuery.uuid; + } else { + id = jQuery.expando; + } + } + + if (!cache[id]) { + cache[id] = {}; + + // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery + // metadata on plain JS objects when the object is serialized using + // JSON.stringify + if (!isNode) { + cache[id].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if (typeof name === "object" || typeof name === "function") { + if (pvt) { + cache[id][internalKey] = jQuery.extend(cache[id][internalKey], name); + } else { + cache[id] = jQuery.extend(cache[id], name); + } + } + + thisCache = cache[id]; + + // Internal jQuery data is stored in a separate object inside the object's data + // cache in order to avoid key collisions between internal data and user-defined + // data + if (pvt) { + if (!thisCache[internalKey]) { + thisCache[internalKey] = {}; + } + + thisCache = thisCache[internalKey]; + } + + if (data !== undefined) { + thisCache[jQuery.camelCase(name)] = data; + } + + // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should + // not attempt to inspect the internal events object using jQuery.data, as this + // internal data object is undocumented and subject to change. + if (name === "events" && !thisCache[name]) { + return thisCache[internalKey] && thisCache[internalKey].events; + } + + return getByName ? thisCache[jQuery.camelCase(name)] : thisCache; + }, + + removeData: function (elem, name, pvt /* Internal Use Only */) { + if (!jQuery.acceptData(elem)) { + return; + } + + var internalKey = jQuery.expando, isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[jQuery.expando] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if (!cache[id]) { + return; + } + + if (name) { + var thisCache = pvt ? cache[id][internalKey] : cache[id]; + + if (thisCache) { + delete thisCache[name]; + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if (!isEmptyDataObject(thisCache)) { + return; + } + } + } + + // See jQuery.data for more information + if (pvt) { + delete cache[id][internalKey]; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if (!isEmptyDataObject(cache[id])) { + return; + } + } + + var internalCache = cache[id][internalKey]; + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + if (jQuery.support.deleteExpando || cache != window) { + delete cache[id]; + } else { + cache[id] = null; + } + + // We destroyed the entire user cache at once because it's faster than + // iterating through each key, but we need to continue to persist internal + // data if it existed + if (internalCache) { + cache[id] = {}; + // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery + // metadata on plain JS objects when the object is serialized using + // JSON.stringify + if (!isNode) { + cache[id].toJSON = jQuery.noop; + } + + cache[id][internalKey] = internalCache; + + // Otherwise, we need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + } else if (isNode) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if (jQuery.support.deleteExpando) { + delete elem[jQuery.expando]; + } else if (elem.removeAttribute) { + elem.removeAttribute(jQuery.expando); + } else { + elem[jQuery.expando] = null; + } + } + }, + + // For internal use only. + _data: function (elem, name, data) { + return jQuery.data(elem, name, data, true); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function (elem) { + if (elem.nodeName) { + var match = jQuery.noData[elem.nodeName.toLowerCase()]; + + if (match) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } + }); + + jQuery.fn.extend({ + data: function (key, value) { + var data = null; + + if (typeof key === "undefined") { + if (this.length) { + data = jQuery.data(this[0]); + + if (this[0].nodeType === 1) { + var attr = this[0].attributes, name; + for (var i = 0, l = attr.length; i < l; i++) { + name = attr[i].name; + + if (name.indexOf("data-") === 0) { + name = jQuery.camelCase(name.substring(5)); + + dataAttr(this[0], name, data[name]); + } + } + } + } + + return data; + + } else if (typeof key === "object") { + return this.each(function () { + jQuery.data(this, key); + }); + } + + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if (value === undefined) { + data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + // Try to fetch any internally stored data first + if (data === undefined && this.length) { + data = jQuery.data(this[0], key); + data = dataAttr(this[0], key, data); + } + + return data === undefined && parts[1] ? + this.data(parts[0]) : + data; + + } else { + return this.each(function () { + var $this = jQuery(this), + args = [parts[0], value]; + + $this.triggerHandler("setData" + parts[1] + "!", args); + jQuery.data(this, key, value); + $this.triggerHandler("changeData" + parts[1] + "!", args); + }); + } + }, + + removeData: function (key) { + return this.each(function () { + jQuery.removeData(this, key); + }); + } + }); + + function dataAttr(elem, key, data) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if (data === undefined && elem.nodeType === 1) { + var name = "data-" + key.replace(rmultiDash, "$1-$2").toLowerCase(); + + data = elem.getAttribute(name); + + if (typeof data === "string") { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + !jQuery.isNaN(data) ? parseFloat(data) : + rbrace.test(data) ? jQuery.parseJSON(data) : + data; + } catch (e) { + } + + // Make sure we set the data so it isn't changed later + jQuery.data(elem, key, data); + + } else { + data = undefined; + } + } + + return data; + } + +// TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON +// property to be considered empty objects; this property always exists in +// order to make sure JSON.stringify does not expose internal metadata + function isEmptyDataObject(obj) { + for (var name in obj) { + if (name !== "toJSON") { + return false; + } + } + + return true; + } + + + function handleQueueMarkDefer(elem, type, src) { + var deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + defer = jQuery.data(elem, deferDataKey, undefined, true); + if (defer && + ( src === "queue" || !jQuery.data(elem, queueDataKey, undefined, true) ) && + ( src === "mark" || !jQuery.data(elem, markDataKey, undefined, true) )) { + // Give room for hard-coded callbacks to fire first + // and eventually mark/queue something else on the element + setTimeout(function () { + if (!jQuery.data(elem, queueDataKey, undefined, true) && !jQuery.data(elem, markDataKey, undefined, true)) { + jQuery.removeData(elem, deferDataKey, true); + defer.resolve(); + } + }, 0); + } + } + + jQuery.extend({ + + _mark: function (elem, type) { + if (elem) { + type = (type || "fx") + "mark"; + jQuery.data(elem, type, (jQuery.data(elem, type, undefined, true) || 0) + 1, true); + } + }, + + _unmark: function (force, elem, type) { + if (force !== true) { + type = elem; + elem = force; + force = false; + } + if (elem) { + type = type || "fx"; + var key = type + "mark", + count = force ? 0 : ( (jQuery.data(elem, key, undefined, true) || 1 ) - 1 ); + if (count) { + jQuery.data(elem, key, count, true); + } else { + jQuery.removeData(elem, key, true); + handleQueueMarkDefer(elem, type, "mark"); + } + } + }, + + queue: function (elem, type, data) { + if (elem) { + type = (type || "fx") + "queue"; + var q = jQuery.data(elem, type, undefined, true); + // Speed up dequeue by getting out quickly if this is just a lookup + if (data) { + if (!q || jQuery.isArray(data)) { + q = jQuery.data(elem, type, jQuery.makeArray(data), true); + } else { + q.push(data); + } + } + return q || []; + } + }, + + dequeue: function (elem, type) { + type = type || "fx"; + + var queue = jQuery.queue(elem, type), + fn = queue.shift(), + defer; + + // If the fx queue is dequeued, always remove the progress sentinel + if (fn === "inprogress") { + fn = queue.shift(); + } + + if (fn) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if (type === "fx") { + queue.unshift("inprogress"); + } + + fn.call(elem, function () { + jQuery.dequeue(elem, type); + }); + } + + if (!queue.length) { + jQuery.removeData(elem, type + "queue", true); + handleQueueMarkDefer(elem, type, "queue"); + } + } + }); + + jQuery.fn.extend({ + queue: function (type, data) { + if (typeof type !== "string") { + data = type; + type = "fx"; + } + + if (data === undefined) { + return jQuery.queue(this[0], type); + } + return this.each(function () { + var queue = jQuery.queue(this, type, data); + + if (type === "fx" && queue[0] !== "inprogress") { + jQuery.dequeue(this, type); + } + }); + }, + dequeue: function (type) { + return this.each(function () { + jQuery.dequeue(this, type); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function (time, type) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || "fx"; + + return this.queue(type, function () { + var elem = this; + setTimeout(function () { + jQuery.dequeue(elem, type); + }, time); + }); + }, + clearQueue: function (type) { + return this.queue(type || "fx", []); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function (type, object) { + if (typeof type !== "string") { + object = type; + type = undefined; + } + type = type || "fx"; + var defer = jQuery.Deferred(), + elements = this, + i = elements.length, + count = 1, + deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + tmp; + + function resolve() { + if (!( --count )) { + defer.resolveWith(elements, [elements]); + } + } + + while (i--) { + if (( tmp = jQuery.data(elements[i], deferDataKey, undefined, true) || + ( jQuery.data(elements[i], queueDataKey, undefined, true) || + jQuery.data(elements[i], markDataKey, undefined, true) ) && + jQuery.data(elements[i], deferDataKey, jQuery._Deferred(), true) )) { + count++; + tmp.done(resolve); + } + } + resolve(); + return defer.promise(); + } + }); + + + var rclass = /[\n\t\r]/g, + rspace = /\s+/, + rreturn = /\r/g, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, + rinvalidChar = /\:/, + formHook, boolHook; + + jQuery.fn.extend({ + attr: function (name, value) { + return jQuery.access(this, name, value, true, jQuery.attr); + }, + + removeAttr: function (name) { + return this.each(function () { + jQuery.removeAttr(this, name); + }); + }, + + prop: function (name, value) { + return jQuery.access(this, name, value, true, jQuery.prop); + }, + + removeProp: function (name) { + name = jQuery.propFix[name] || name; + return this.each(function () { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[name] = undefined; + delete this[name]; + } catch (e) { + } + }); + }, + + addClass: function (value) { + if (jQuery.isFunction(value)) { + return this.each(function (i) { + var self = jQuery(this); + self.addClass(value.call(this, i, self.attr("class") || "")); + }); + } + + if (value && typeof value === "string") { + var classNames = (value || "").split(rspace); + + for (var i = 0, l = this.length; i < l; i++) { + var elem = this[i]; + + if (elem.nodeType === 1) { + if (!elem.className) { + elem.className = value; + + } else { + var className = " " + elem.className + " ", + setClass = elem.className; + + for (var c = 0, cl = classNames.length; c < cl; c++) { + if (className.indexOf(" " + classNames[c] + " ") < 0) { + setClass += " " + classNames[c]; + } + } + elem.className = jQuery.trim(setClass); + } + } + } + } + + return this; + }, + + removeClass: function (value) { + if (jQuery.isFunction(value)) { + return this.each(function (i) { + var self = jQuery(this); + self.removeClass(value.call(this, i, self.attr("class"))); + }); + } + + if ((value && typeof value === "string") || value === undefined) { + var classNames = (value || "").split(rspace); + + for (var i = 0, l = this.length; i < l; i++) { + var elem = this[i]; + + if (elem.nodeType === 1 && elem.className) { + if (value) { + var className = (" " + elem.className + " ").replace(rclass, " "); + for (var c = 0, cl = classNames.length; c < cl; c++) { + className = className.replace(" " + classNames[c] + " ", " "); + } + elem.className = jQuery.trim(className); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function (value, stateVal) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if (jQuery.isFunction(value)) { + return this.each(function (i) { + var self = jQuery(this); + self.toggleClass(value.call(this, i, self.attr("class"), stateVal), stateVal); + }); + } + + return this.each(function () { + if (type === "string") { + // toggle individual class names + var className, + i = 0, + self = jQuery(this), + state = stateVal, + classNames = value.split(rspace); + + while ((className = classNames[i++])) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass(className); + self[state ? "addClass" : "removeClass"](className); + } + + } else if (type === "undefined" || type === "boolean") { + if (this.className) { + // store className if set + jQuery._data(this, "__className__", this.className); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery._data(this, "__className__") || ""; + } + }); + }, + + hasClass: function (selector) { + var className = " " + selector + " "; + for (var i = 0, l = this.length; i < l; i++) { + if ((" " + this[i].className + " ").replace(rclass, " ").indexOf(className) > -1) { + return true; + } + } + + return false; + }, + + val: function (value) { + var hooks, ret, + elem = this[0]; + + if (!arguments.length) { + if (elem) { + hooks = jQuery.valHooks[elem.nodeName.toLowerCase()] || jQuery.valHooks[elem.type]; + + if (hooks && "get" in hooks && (ret = hooks.get(elem, "value")) !== undefined) { + return ret; + } + + return (elem.value || "").replace(rreturn, ""); + } + + return undefined; + } + + var isFunction = jQuery.isFunction(value); + + return this.each(function (i) { + var self = jQuery(this), val; + + if (this.nodeType !== 1) { + return; + } + + if (isFunction) { + val = value.call(this, i, self.val()); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if (val == null) { + val = ""; + } else if (typeof val === "number") { + val += ""; + } else if (jQuery.isArray(val)) { + val = jQuery.map(val, function (value) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[this.nodeName.toLowerCase()] || jQuery.valHooks[this.type]; + + // If set returns undefined, fall back to normal setting + if (!hooks || !("set" in hooks) || hooks.set(this, val, "value") === undefined) { + this.value = val; + } + }); + } + }); + + jQuery.extend({ + valHooks: { + option: { + get: function (elem) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function (elem) { + var value, + index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if (index < 0) { + return null; + } + + // Loop through all the selected options + for (var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++) { + var option = options[i]; + + // Don't return options that are disabled or in a disabled optgroup + if (option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName(option.parentNode, "optgroup"))) { + + // Get the specific value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if (one) { + return value; + } + + // Multi-Selects return an array + values.push(value); + } + } + + // Fixes Bug #2551 -- select.val() broken in IE after form.reset() + if (one && !values.length && options.length) { + return jQuery(options[index]).val(); + } + + return values; + }, + + set: function (elem, value) { + var values = jQuery.makeArray(value); + + jQuery(elem).find("option").each(function () { + this.selected = jQuery.inArray(jQuery(this).val(), values) >= 0; + }); + + if (!values.length) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attrFix: { + // Always normalize to ensure hook usage + tabindex: "tabIndex" + }, + + attr: function (elem, name, value, pass) { + var nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if (!elem || nType === 3 || nType === 8 || nType === 2) { + return undefined; + } + + if (pass && name in jQuery.attrFn) { + return jQuery(elem)[name](value); + } + + // Fallback to prop when attributes are not supported + if (!("getAttribute" in elem)) { + return jQuery.prop(elem, name, value); + } + + var ret, hooks, + notxml = nType !== 1 || !jQuery.isXMLDoc(elem); + + // Normalize the name if needed + name = notxml && jQuery.attrFix[name] || name; + + hooks = jQuery.attrHooks[name]; + + if (!hooks) { + // Use boolHook for boolean attributes + if (rboolean.test(name) && + (typeof value === "boolean" || value === undefined || value.toLowerCase() === name.toLowerCase())) { + + hooks = boolHook; + + // Use formHook for forms and if the name contains certain characters + } else if (formHook && (jQuery.nodeName(elem, "form") || rinvalidChar.test(name))) { + hooks = formHook; + } + } + + if (value !== undefined) { + + if (value === null) { + jQuery.removeAttr(elem, name); + return undefined; + + } else if (hooks && "set" in hooks && notxml && (ret = hooks.set(elem, value, name)) !== undefined) { + return ret; + + } else { + elem.setAttribute(name, "" + value); + return value; + } + + } else if (hooks && "get" in hooks && notxml) { + return hooks.get(elem, name); + + } else { + + ret = elem.getAttribute(name); + + // Non-existent attributes return null, we normalize to undefined + return ret === null ? + undefined : + ret; + } + }, + + removeAttr: function (elem, name) { + var propName; + if (elem.nodeType === 1) { + name = jQuery.attrFix[name] || name; + + if (jQuery.support.getSetAttribute) { + // Use removeAttribute in browsers that support it + elem.removeAttribute(name); + } else { + jQuery.attr(elem, name, ""); + elem.removeAttributeNode(elem.getAttributeNode(name)); + } + + // Set corresponding property to false for boolean attributes + if (rboolean.test(name) && (propName = jQuery.propFix[name] || name) in elem) { + elem[propName] = false; + } + } + }, + + attrHooks: { + type: { + set: function (elem, value) { + // We can't allow the type property to be changed (since it causes problems in IE) + if (rtype.test(elem.nodeName) && elem.parentNode) { + jQuery.error("type property can't be changed"); + } else if (!jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input")) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to it's default in case type is set after value + // This is for element creation + var val = elem.value; + elem.setAttribute("type", value); + if (val) { + elem.value = val; + } + return value; + } + } + }, + tabIndex: { + get: function (elem) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabIndex"); + + return attributeNode && attributeNode.specified ? + parseInt(attributeNode.value, 10) : + rfocusable.test(elem.nodeName) || rclickable.test(elem.nodeName) && elem.href ? + 0 : + undefined; + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function (elem, name, value) { + var nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if (!elem || nType === 3 || nType === 8 || nType === 2) { + return undefined; + } + + var ret, hooks, + notxml = nType !== 1 || !jQuery.isXMLDoc(elem); + + // Try to normalize/fix the name + name = notxml && jQuery.propFix[name] || name; + + hooks = jQuery.propHooks[name]; + + if (value !== undefined) { + if (hooks && "set" in hooks && (ret = hooks.set(elem, value, name)) !== undefined) { + return ret; + + } else { + return (elem[name] = value); + } + + } else { + if (hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== undefined) { + return ret; + + } else { + return elem[name]; + } + } + }, + + propHooks: {} + }); + +// Hook for boolean attributes + boolHook = { + get: function (elem, name) { + // Align boolean attributes with corresponding properties + return elem[jQuery.propFix[name] || name] ? + name.toLowerCase() : + undefined; + }, + set: function (elem, value, name) { + var propName; + if (value === false) { + // Remove boolean attributes when set to false + jQuery.removeAttr(elem, name); + } else { + // value is true since we know at this point it's type boolean and not false + // Set boolean attributes to the same name and set the DOM property + propName = jQuery.propFix[name] || name; + if (propName in elem) { + // Only set the IDL specifically if it already exists on the element + elem[propName] = value; + } + + elem.setAttribute(name, name.toLowerCase()); + } + return name; + } + }; + +// Use the value property for back compat +// Use the formHook for button elements in IE6/7 (#1954) + jQuery.attrHooks.value = { + get: function (elem, name) { + if (formHook && jQuery.nodeName(elem, "button")) { + return formHook.get(elem, name); + } + return elem.value; + }, + set: function (elem, value, name) { + if (formHook && jQuery.nodeName(elem, "button")) { + return formHook.set(elem, value, name); + } + // Does not return so that setAttribute is also used + elem.value = value; + } + }; + +// IE6/7 do not support getting/setting some attributes with get/setAttribute + if (!jQuery.support.getSetAttribute) { + + // propFix is more comprehensive and contains all fixes + jQuery.attrFix = jQuery.propFix; + + // Use this for any attribute on a form in IE6/7 + formHook = jQuery.attrHooks.name = jQuery.valHooks.button = { + get: function (elem, name) { + var ret; + ret = elem.getAttributeNode(name); + // Return undefined if nodeValue is empty string + return ret && ret.nodeValue !== "" ? + ret.nodeValue : + undefined; + }, + set: function (elem, value, name) { + // Check form objects in IE (multiple bugs related) + // Only use nodeValue if the attribute node exists on the form + var ret = elem.getAttributeNode(name); + if (ret) { + ret.nodeValue = value; + return value; + } + } + }; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each(["width", "height"], function (i, name) { + jQuery.attrHooks[name] = jQuery.extend(jQuery.attrHooks[name], { + set: function (elem, value) { + if (value === "") { + elem.setAttribute(name, "auto"); + return value; + } + } + }); + }); + } + + +// Some attributes require a special call on IE + if (!jQuery.support.hrefNormalized) { + jQuery.each(["href", "src", "width", "height"], function (i, name) { + jQuery.attrHooks[name] = jQuery.extend(jQuery.attrHooks[name], { + get: function (elem) { + var ret = elem.getAttribute(name, 2); + return ret === null ? undefined : ret; + } + }); + }); + } + + if (!jQuery.support.style) { + jQuery.attrHooks.style = { + get: function (elem) { + // Return undefined in the case of empty string + // Normalize to lowercase since IE uppercases css property names + return elem.style.cssText.toLowerCase() || undefined; + }, + set: function (elem, value) { + return (elem.style.cssText = "" + value); + } + }; + } + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it + if (!jQuery.support.optSelected) { + jQuery.propHooks.selected = jQuery.extend(jQuery.propHooks.selected, { + get: function (elem) { + var parent = elem.parentNode; + + if (parent) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if (parent.parentNode) { + parent.parentNode.selectedIndex; + } + } + } + }); + } + +// Radios and checkboxes getter/setter + if (!jQuery.support.checkOn) { + jQuery.each(["radio", "checkbox"], function () { + jQuery.valHooks[this] = { + get: function (elem) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); + } + jQuery.each(["radio", "checkbox"], function () { + jQuery.valHooks[this] = jQuery.extend(jQuery.valHooks[this], { + set: function (elem, value) { + if (jQuery.isArray(value)) { + return (elem.checked = jQuery.inArray(jQuery(elem).val(), value) >= 0); + } + } + }); + }); + + + var hasOwn = Object.prototype.hasOwnProperty, + rnamespaces = /\.(.*)$/, + rformElems = /^(?:textarea|input|select)$/i, + rperiod = /\./g, + rspaces = / /g, + rescape = /[^\w\s.|`]/g, + fcleanup = function (nm) { + return nm.replace(rescape, "\\$&"); + }; + + /* + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. + */ + jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function (elem, types, handler, data) { + if (elem.nodeType === 3 || elem.nodeType === 8) { + return; + } + + if (handler === false) { + handler = returnFalse; + } else if (!handler) { + // Fixes bug #7229. Fix recommended by jdalton + return; + } + + var handleObjIn, handleObj; + + if (handler.handler) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the function being executed has a unique ID + if (!handler.guid) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure + var elemData = jQuery._data(elem); + + // If no elemData is found then we must be trying to bind to one of the + // banned noData elements + if (!elemData) { + return; + } + + var events = elemData.events, + eventHandle = elemData.handle; + + if (!events) { + elemData.events = events = {}; + } + + if (!eventHandle) { + elemData.handle = eventHandle = function (e) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.handle.apply(eventHandle.elem, arguments) : + undefined; + }; + } + + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native events in IE. + eventHandle.elem = elem; + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = types.split(" "); + + var type, i = 0, namespaces; + + while ((type = types[i++])) { + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + {handler: handler, data: data}; + + // Namespaced event handlers + if (type.indexOf(".") > -1) { + namespaces = type.split("."); + type = namespaces.shift(); + handleObj.namespace = namespaces.slice(0).sort().join("."); + + } else { + namespaces = []; + handleObj.namespace = ""; + } + + handleObj.type = type; + if (!handleObj.guid) { + handleObj.guid = handler.guid; + } + + // Get the current list of functions bound to this event + var handlers = events[type], + special = jQuery.event.special[type] || {}; + + // Init the event handler queue + if (!handlers) { + handlers = events[type] = []; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) { + // Bind the global event handler to the element + if (elem.addEventListener) { + elem.addEventListener(type, eventHandle, false); + + } else if (elem.attachEvent) { + elem.attachEvent("on" + type, eventHandle); + } + } + } + + if (special.add) { + special.add.call(elem, handleObj); + + if (!handleObj.handler.guid) { + handleObj.handler.guid = handler.guid; + } + } + + // Add the function to the element's handler list + handlers.push(handleObj); + + // Keep track of which events have been used, for event optimization + jQuery.event.global[type] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function (elem, types, handler, pos) { + // don't do events on text and comment nodes + if (elem.nodeType === 3 || elem.nodeType === 8) { + return; + } + + if (handler === false) { + handler = returnFalse; + } + + var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, + elemData = jQuery.hasData(elem) && jQuery._data(elem), + events = elemData && elemData.events; + + if (!elemData || !events) { + return; + } + + // types is actually an event object here + if (types && types.type) { + handler = types.handler; + types = types.type; + } + + // Unbind all events for the element + if (!types || typeof types === "string" && types.charAt(0) === ".") { + types = types || ""; + + for (type in events) { + jQuery.event.remove(elem, type + types); + } + + return; + } + + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(" "); + + while ((type = types[i++])) { + origType = type; + handleObj = null; + all = type.indexOf(".") < 0; + namespaces = []; + + if (!all) { + // Namespaced event handlers + namespaces = type.split("."); + type = namespaces.shift(); + + namespace = new RegExp("(^|\\.)" + + jQuery.map(namespaces.slice(0).sort(), fcleanup).join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + eventType = events[type]; + + if (!eventType) { + continue; + } + + if (!handler) { + for (j = 0; j < eventType.length; j++) { + handleObj = eventType[j]; + + if (all || namespace.test(handleObj.namespace)) { + jQuery.event.remove(elem, origType, handleObj.handler, j); + eventType.splice(j--, 1); + } + } + + continue; + } + + special = jQuery.event.special[type] || {}; + + for (j = pos || 0; j < eventType.length; j++) { + handleObj = eventType[j]; + + if (handler.guid === handleObj.guid) { + // remove the given handler for the given type + if (all || namespace.test(handleObj.namespace)) { + if (pos == null) { + eventType.splice(j--, 1); + } + + if (special.remove) { + special.remove.call(elem, handleObj); + } + } + + if (pos != null) { + break; + } + } + } + + // remove generic event handler if no more handlers exist + if (eventType.length === 0 || pos != null && eventType.length === 1) { + if (!special.teardown || special.teardown.call(elem, namespaces) === false) { + jQuery.removeEvent(elem, type, elemData.handle); + } + + ret = null; + delete events[type]; + } + } + + // Remove the expando if it's no longer used + if (jQuery.isEmptyObject(events)) { + var handle = elemData.handle; + if (handle) { + handle.elem = null; + } + + delete elemData.events; + delete elemData.handle; + + if (jQuery.isEmptyObject(elemData)) { + jQuery.removeData(elem, undefined, true); + } + } + }, + + // Events that are safe to short-circuit if no handlers are attached. + // Native DOM events should not be added, they may have inline handlers. + customEvent: { + "getData": true, + "setData": true, + "changeData": true + }, + + trigger: function (event, data, elem, onlyHandlers) { + // Event object or event type + var type = event.type || event, + namespaces = [], + exclusive; + + if (type.indexOf("!") >= 0) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } + + if (type.indexOf(".") >= 0) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + + if ((!elem || jQuery.event.customEvent[type]) && !jQuery.event.global[type]) { + // No jQuery handlers for this event type, and it can't have inline handlers + return; + } + + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[jQuery.expando] ? event : + // Object literal + new jQuery.Event(type, event) : + // Just the event type (string) + new jQuery.Event(type); + + event.type = type; + event.exclusive = exclusive; + event.namespace = namespaces.join("."); + event.namespace_re = new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)"); + + // triggerHandler() and global events don't bubble or run the default action + if (onlyHandlers || !elem) { + event.preventDefault(); + event.stopPropagation(); + } + + // Handle a global trigger + if (!elem) { + // TODO: Stop taunting the data cache; remove global events and always attach to document + jQuery.each(jQuery.cache, function () { + // internalKey variable is just used to make it easier to find + // and potentially change this stuff later; currently it just + // points to jQuery.expando + var internalKey = jQuery.expando, + internalCache = this[internalKey]; + if (internalCache && internalCache.events && internalCache.events[type]) { + jQuery.event.trigger(event, data, internalCache.handle.elem); + } + }); + return; + } + + // Don't do events on text and comment nodes + if (elem.nodeType === 3 || elem.nodeType === 8) { + return; + } + + // Clean up the event in case it is being reused + event.result = undefined; + event.target = elem; + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data ? jQuery.makeArray(data) : []; + data.unshift(event); + + var cur = elem, + // IE doesn't like method names with a colon (#3533, #8272) + ontype = type.indexOf(":") < 0 ? "on" + type : ""; + + // Fire event on the current element, then bubble up the DOM tree + do { + var handle = jQuery._data(cur, "handle"); + + event.currentTarget = cur; + if (handle) { + handle.apply(cur, data); + } + + // Trigger an inline bound script + if (ontype && jQuery.acceptData(cur) && cur[ontype] && cur[ontype].apply(cur, data) === false) { + event.result = false; + event.preventDefault(); + } + + // Bubble up to document, then to window + cur = cur.parentNode || cur.ownerDocument || cur === event.target.ownerDocument && window; + } while (cur && !event.isPropagationStopped()); + + // If nobody prevented the default action, do it now + if (!event.isDefaultPrevented()) { + var old, + special = jQuery.event.special[type] || {}; + + if ((!special._default || special._default.call(elem.ownerDocument, event) === false) && !(type === "click" && jQuery.nodeName(elem, "a")) && jQuery.acceptData(elem)) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction)() check here because IE6/7 fails that test. + // IE<9 dies on focus to hidden element (#1486), may want to revisit a try/catch. + try { + if (ontype && elem[type]) { + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ontype]; + + if (old) { + elem[ontype] = null; + } + + jQuery.event.triggered = type; + elem[type](); + } + } catch (ieError) { + } + + if (old) { + elem[ontype] = old; + } + + jQuery.event.triggered = undefined; + } + } + + return event.result; + }, + + handle: function (event) { + event = jQuery.event.fix(event || window.event); + // Snapshot the handlers list since a called handler may add/remove events. + var handlers = ((jQuery._data(this, "events") || {})[event.type] || []).slice(0), + run_all = !event.exclusive && !event.namespace, + args = Array.prototype.slice.call(arguments, 0); + + // Use the fix-ed Event rather than the (read-only) native event + args[0] = event; + event.currentTarget = this; + + for (var j = 0, l = handlers.length; j < l; j++) { + var handleObj = handlers[j]; + + // Triggered event must 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event. + if (run_all || event.namespace_re.test(handleObj.namespace)) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var ret = handleObj.handler.apply(this, args); + + if (ret !== undefined) { + event.result = ret; + if (ret === false) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if (event.isImmediatePropagationStopped()) { + break; + } + } + } + return event.result; + }, + + props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + + fix: function (event) { + if (event[jQuery.expando]) { + return event; + } + + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; + event = jQuery.Event(originalEvent); + + for (var i = this.props.length, prop; i;) { + prop = this.props[--i]; + event[prop] = originalEvent[prop]; + } + + // Fix target property, if necessary + if (!event.target) { + // Fixes #1925 where srcElement might not be defined either + event.target = event.srcElement || document; + } + + // check if target is a textnode (safari) + if (event.target.nodeType === 3) { + event.target = event.target.parentNode; + } + + // Add relatedTarget, if necessary + if (!event.relatedTarget && event.fromElement) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Calculate pageX/Y if missing and clientX/Y available + if (event.pageX == null && event.clientX != null) { + var eventDocument = event.target.ownerDocument || document, + doc = eventDocument.documentElement, + body = eventDocument.body; + + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add which for key events + if (event.which == null && (event.charCode != null || event.keyCode != null)) { + event.which = event.charCode != null ? event.charCode : event.keyCode; + } + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if (!event.metaKey && event.ctrlKey) { + event.metaKey = event.ctrlKey; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if (!event.which && event.button !== undefined) { + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + } + + return event; + }, + + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead + proxy: jQuery.proxy, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady, + teardown: jQuery.noop + }, + + live: { + add: function (handleObj) { + jQuery.event.add(this, + liveConvert(handleObj.origType, handleObj.selector), + jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid})); + }, + + remove: function (handleObj) { + jQuery.event.remove(this, liveConvert(handleObj.origType, handleObj.selector), handleObj); + } + }, + + beforeunload: { + setup: function (data, namespaces, eventHandle) { + // We only want to do this special case on windows + if (jQuery.isWindow(this)) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function (namespaces, eventHandle) { + if (this.onbeforeunload === eventHandle) { + this.onbeforeunload = null; + } + } + } + } + }; + + jQuery.removeEvent = document.removeEventListener ? + function (elem, type, handle) { + if (elem.removeEventListener) { + elem.removeEventListener(type, handle, false); + } + } : + function (elem, type, handle) { + if (elem.detachEvent) { + elem.detachEvent("on" + type, handle); + } + }; + + jQuery.Event = function (src, props) { + // Allow instantiation without the 'new' keyword + if (!this.preventDefault) { + return new jQuery.Event(src, props); + } + + // Event object + if (src && src.type) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if (props) { + jQuery.extend(this, props); + } + + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = jQuery.now(); + + // Mark it as fixed + this[jQuery.expando] = true; + }; + + function returnFalse() { + return false; + } + + function returnTrue() { + return true; + } + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html + jQuery.Event.prototype = { + preventDefault: function () { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if (!e) { + return; + } + + // if preventDefault exists run it on the original event + if (e.preventDefault) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function () { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if (!e) { + return; + } + // if stopPropagation exists run it on the original event + if (e.stopPropagation) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function () { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse + }; + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers + var withinElement = function (event) { + // Check if mouse(over|out) are still within the same parent element + var parent = event.relatedTarget; + + // set the correct event type + event.type = event.data; + + // Firefox sometimes assigns relatedTarget a XUL element + // which we cannot access the parentNode property of + try { + + // Chrome does something similar, the parentNode property + // can be accessed but is null. + if (parent && parent !== document && !parent.parentNode) { + return; + } + + // Traverse up the tree + while (parent && parent !== this) { + parent = parent.parentNode; + } + + if (parent !== this) { + // handle event if we actually just moused on to a non sub-element + jQuery.event.handle.apply(this, arguments); + } + + // assuming we've left the element since we most likely mousedover a xul element + } catch (e) { + } + }, + +// In case of event delegation, we only need to rename the event.type, +// liveHandler will take care of the rest. + delegate = function (event) { + event.type = event.data; + jQuery.event.handle.apply(this, arguments); + }; + +// Create mouseenter and mouseleave events + jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" + }, function (orig, fix) { + jQuery.event.special[orig] = { + setup: function (data) { + jQuery.event.add(this, fix, data && data.selector ? delegate : withinElement, orig); + }, + teardown: function (data) { + jQuery.event.remove(this, fix, data && data.selector ? delegate : withinElement); + } + }; + }); + +// submit delegation + if (!jQuery.support.submitBubbles) { + + jQuery.event.special.submit = { + setup: function (data, namespaces) { + if (!jQuery.nodeName(this, "form")) { + jQuery.event.add(this, "click.specialSubmit", function (e) { + var elem = e.target, + type = elem.type; + + if ((type === "submit" || type === "image") && jQuery(elem).closest("form").length) { + trigger("submit", this, arguments); + } + }); + + jQuery.event.add(this, "keypress.specialSubmit", function (e) { + var elem = e.target, + type = elem.type; + + if ((type === "text" || type === "password") && jQuery(elem).closest("form").length && e.keyCode === 13) { + trigger("submit", this, arguments); + } + }); + + } else { + return false; + } + }, + + teardown: function (namespaces) { + jQuery.event.remove(this, ".specialSubmit"); + } + }; + + } + +// change delegation, happens here so we have bind. + if (!jQuery.support.changeBubbles) { + + var changeFilters, + + getVal = function (elem) { + var type = elem.type, val = elem.value; + + if (type === "radio" || type === "checkbox") { + val = elem.checked; + + } else if (type === "select-multiple") { + val = elem.selectedIndex > -1 ? + jQuery.map(elem.options, function (elem) { + return elem.selected; + }).join("-") : + ""; + + } else if (jQuery.nodeName(elem, "select")) { + val = elem.selectedIndex; + } + + return val; + }, + + testChange = function testChange(e) { + var elem = e.target, data, val; + + if (!rformElems.test(elem.nodeName) || elem.readOnly) { + return; + } + + data = jQuery._data(elem, "_change_data"); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if (e.type !== "focusout" || elem.type !== "radio") { + jQuery._data(elem, "_change_data", val); + } + + if (data === undefined || val === data) { + return; + } + + if (data != null || val) { + e.type = "change"; + e.liveFired = undefined; + jQuery.event.trigger(e, arguments[1], elem); + } + }; + + jQuery.event.special.change = { + filters: { + focusout: testChange, + + beforedeactivate: testChange, + + click: function (e) { + var elem = e.target, type = jQuery.nodeName(elem, "input") ? elem.type : ""; + + if (type === "radio" || type === "checkbox" || jQuery.nodeName(elem, "select")) { + testChange.call(this, e); + } + }, + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + keydown: function (e) { + var elem = e.target, type = jQuery.nodeName(elem, "input") ? elem.type : ""; + + if ((e.keyCode === 13 && !jQuery.nodeName(elem, "textarea") ) || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple") { + testChange.call(this, e); + } + }, + + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information + beforeactivate: function (e) { + var elem = e.target; + jQuery._data(elem, "_change_data", getVal(elem)); + } + }, + + setup: function (data, namespaces) { + if (this.type === "file") { + return false; + } + + for (var type in changeFilters) { + jQuery.event.add(this, type + ".specialChange", changeFilters[type]); + } + + return rformElems.test(this.nodeName); + }, + + teardown: function (namespaces) { + jQuery.event.remove(this, ".specialChange"); + + return rformElems.test(this.nodeName); + } + }; + + changeFilters = jQuery.event.special.change.filters; + + // Handle when the input is .focus()'d + changeFilters.focus = changeFilters.beforeactivate; + } + + function trigger(type, elem, args) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + // Don't pass args or remember liveFired; they apply to the donor event. + var event = jQuery.extend({}, args[0]); + event.type = type; + event.originalEvent = {}; + event.liveFired = undefined; + jQuery.event.handle.call(elem, event); + if (event.isDefaultPrevented()) { + args[0].preventDefault(); + } + } + +// Create "bubbling" focus and blur events + if (!jQuery.support.focusinBubbles) { + jQuery.each({focus: "focusin", blur: "focusout"}, function (orig, fix) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0; + + jQuery.event.special[fix] = { + setup: function () { + if (attaches++ === 0) { + document.addEventListener(orig, handler, true); + } + }, + teardown: function () { + if (--attaches === 0) { + document.removeEventListener(orig, handler, true); + } + } + }; + + function handler(donor) { + // Donor event is always a native one; fix it and switch its type. + // Let focusin/out handler cancel the donor focus/blur event. + var e = jQuery.event.fix(donor); + e.type = fix; + e.originalEvent = {}; + jQuery.event.trigger(e, null, e.target); + if (e.isDefaultPrevented()) { + donor.preventDefault(); + } + } + }); + } + + jQuery.each(["bind", "one"], function (i, name) { + jQuery.fn[name] = function (type, data, fn) { + var handler; + + // Handle object literals + if (typeof type === "object") { + for (var key in type) { + this[name](key, data, type[key], fn); + } + return this; + } + + if (arguments.length === 2 || data === false) { + fn = data; + data = undefined; + } + + if (name === "one") { + handler = function (event) { + jQuery(this).unbind(event, handler); + return fn.apply(this, arguments); + }; + handler.guid = fn.guid || jQuery.guid++; + } else { + handler = fn; + } + + if (type === "unload" && name !== "one") { + this.one(type, data, fn); + + } else { + for (var i = 0, l = this.length; i < l; i++) { + jQuery.event.add(this[i], type, handler, data); + } + } + + return this; + }; + }); + + jQuery.fn.extend({ + unbind: function (type, fn) { + // Handle object literals + if (typeof type === "object" && !type.preventDefault) { + for (var key in type) { + this.unbind(key, type[key]); + } + + } else { + for (var i = 0, l = this.length; i < l; i++) { + jQuery.event.remove(this[i], type, fn); + } + } + + return this; + }, + + delegate: function (selector, types, data, fn) { + return this.live(types, data, fn, selector); + }, + + undelegate: function (selector, types, fn) { + if (arguments.length === 0) { + return this.unbind("live"); + + } else { + return this.die(types, null, fn, selector); + } + }, + + trigger: function (type, data) { + return this.each(function () { + jQuery.event.trigger(type, data, this); + }); + }, + + triggerHandler: function (type, data) { + if (this[0]) { + return jQuery.event.trigger(type, data, this[0], true); + } + }, + + toggle: function (fn) { + // Save reference to arguments for access in closure + var args = arguments, + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function (event) { + // Figure out which function to execute + var lastToggle = ( jQuery.data(this, "lastToggle" + fn.guid) || 0 ) % i; + jQuery.data(this, "lastToggle" + fn.guid, lastToggle + 1); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[lastToggle].apply(this, arguments) || false; + }; + + // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; + while (i < args.length) { + args[i++].guid = guid; + } + + return this.click(toggler); + }, + + hover: function (fnOver, fnOut) { + return this.mouseenter(fnOver).mouseleave(fnOut || fnOver); + } + }); + + var liveMap = { + focus: "focusin", + blur: "focusout", + mouseenter: "mouseover", + mouseleave: "mouseout" + }; + + jQuery.each(["live", "die"], function (i, name) { + jQuery.fn[name] = function (types, data, fn, origSelector /* Internal Use Only */) { + var type, i = 0, match, namespaces, preType, + selector = origSelector || this.selector, + context = origSelector ? this : jQuery(this.context); + + if (typeof types === "object" && !types.preventDefault) { + for (var key in types) { + context[name](key, data, types[key], selector); + } + + return this; + } + + if (name === "die" && !types && + origSelector && origSelector.charAt(0) === ".") { + + context.unbind(origSelector); + + return this; + } + + if (data === false || jQuery.isFunction(data)) { + fn = data || returnFalse; + data = undefined; + } + + types = (types || "").split(" "); + + while ((type = types[i++]) != null) { + match = rnamespaces.exec(type); + namespaces = ""; + + if (match) { + namespaces = match[0]; + type = type.replace(rnamespaces, ""); + } + + if (type === "hover") { + types.push("mouseenter" + namespaces, "mouseleave" + namespaces); + continue; + } + + preType = type; + + if (liveMap[type]) { + types.push(liveMap[type] + namespaces); + type = type + namespaces; + + } else { + type = (liveMap[type] || type) + namespaces; + } + + if (name === "live") { + // bind live handler + for (var j = 0, l = context.length; j < l; j++) { + jQuery.event.add(context[j], "live." + liveConvert(type, selector), + { + data: data, + selector: selector, + handler: fn, + origType: type, + origHandler: fn, + preType: preType + }); + } + + } else { + // unbind live handler + context.unbind("live." + liveConvert(type, selector), fn); + } + } + + return this; + }; + }); + + function liveHandler(event) { + var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, + elems = [], + selectors = [], + events = jQuery._data(this, "events"); + + // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911) + if (event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click") { + return; + } + + if (event.namespace) { + namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.liveFired = this; + + var live = events.live.slice(0); + + for (j = 0; j < live.length; j++) { + handleObj = live[j]; + + if (handleObj.origType.replace(rnamespaces, "") === event.type) { + selectors.push(handleObj.selector); + + } else { + live.splice(j--, 1); + } + } + + match = jQuery(event.target).closest(selectors, event.currentTarget); + + for (i = 0, l = match.length; i < l; i++) { + close = match[i]; + + for (j = 0; j < live.length; j++) { + handleObj = live[j]; + + if (close.selector === handleObj.selector && (!namespace || namespace.test(handleObj.namespace)) && !close.elem.disabled) { + elem = close.elem; + related = null; + + // Those two events require additional checking + if (handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave") { + event.type = handleObj.preType; + related = jQuery(event.relatedTarget).closest(handleObj.selector)[0]; + + // Make sure not to accidentally match a child element with the same selector + if (related && jQuery.contains(elem, related)) { + related = elem; + } + } + + if (!related || related !== elem) { + elems.push({elem: elem, handleObj: handleObj, level: close.level}); + } + } + } + } + + for (i = 0, l = elems.length; i < l; i++) { + match = elems[i]; + + if (maxLevel && match.level > maxLevel) { + break; + } + + event.currentTarget = match.elem; + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + ret = match.handleObj.origHandler.apply(match.elem, arguments); + + if (ret === false || event.isPropagationStopped()) { + maxLevel = match.level; + + if (ret === false) { + stop = false; + } + if (event.isImmediatePropagationStopped()) { + break; + } + } + } + + return stop; + } + + function liveConvert(type, selector) { + return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspaces, "&"); + } + + jQuery.each(("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error").split(" "), function (i, name) { + + // Handle event binding + jQuery.fn[name] = function (data, fn) { + if (fn == null) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.bind(name, data, fn) : + this.trigger(name); + }; + + if (jQuery.attrFn) { + jQuery.attrFn[name] = true; + } + }); + + + /*! + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ + (function () { + + var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true, + rBackslash = /\\/g, + rNonWord = /\W/; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. + [0, 0].sort(function () { + baseHasDuplicate = false; + return 0; + }); + + var Sizzle = function (selector, context, results, seed) { + results = results || []; + context = context || document; + + var origContext = context; + + if (context.nodeType !== 1 && context.nodeType !== 9) { + return []; + } + + if (!selector || typeof selector !== "string") { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML(context), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec(""); + m = chunker.exec(soFar); + + if (m) { + soFar = m[3]; + + parts.push(m[1]); + + if (m[2]) { + extra = m[3]; + break; + } + } + } while (m); + + if (parts.length > 1 && origPOS.exec(selector)) { + + if (parts.length === 2 && Expr.relative[parts[0]]) { + set = posProcess(parts[0] + parts[1], context); + + } else { + set = Expr.relative[parts[0]] ? + [context] : + Sizzle(parts.shift(), context); + + while (parts.length) { + selector = parts.shift(); + + if (Expr.relative[selector]) { + selector += parts.shift(); + } + + set = posProcess(selector, set); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if (!seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1])) { + + ret = Sizzle.find(parts.shift(), context, contextXML); + context = ret.expr ? + Sizzle.filter(ret.expr, ret.set)[0] : + ret.set[0]; + } + + if (context) { + ret = seed ? + {expr: parts.pop(), set: makeArray(seed)} : + Sizzle.find(parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML); + + set = ret.expr ? + Sizzle.filter(ret.expr, ret.set) : + ret.set; + + if (parts.length > 0) { + checkSet = makeArray(set); + + } else { + prune = false; + } + + while (parts.length) { + cur = parts.pop(); + pop = cur; + + if (!Expr.relative[cur]) { + cur = ""; + } else { + pop = parts.pop(); + } + + if (pop == null) { + pop = context; + } + + Expr.relative[cur](checkSet, pop, contextXML); + } + + } else { + checkSet = parts = []; + } + } + + if (!checkSet) { + checkSet = set; + } + + if (!checkSet) { + Sizzle.error(cur || selector); + } + + if (toString.call(checkSet) === "[object Array]") { + if (!prune) { + results.push.apply(results, checkSet); + + } else if (context && context.nodeType === 1) { + for (i = 0; checkSet[i] != null; i++) { + if (checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i]))) { + results.push(set[i]); + } + } + + } else { + for (i = 0; checkSet[i] != null; i++) { + if (checkSet[i] && checkSet[i].nodeType === 1) { + results.push(set[i]); + } + } + } + + } else { + makeArray(checkSet, results); + } + + if (extra) { + Sizzle(extra, origContext, results, seed); + Sizzle.uniqueSort(results); + } + + return results; + }; + + Sizzle.uniqueSort = function (results) { + if (sortOrder) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if (hasDuplicate) { + for (var i = 1; i < results.length; i++) { + if (results[i] === results[i - 1]) { + results.splice(i--, 1); + } + } + } + } + + return results; + }; + + Sizzle.matches = function (expr, set) { + return Sizzle(expr, null, null, set); + }; + + Sizzle.matchesSelector = function (node, expr) { + return Sizzle(expr, null, null, [node]).length > 0; + }; + + Sizzle.find = function (expr, context, isXML) { + var set; + + if (!expr) { + return []; + } + + for (var i = 0, l = Expr.order.length; i < l; i++) { + var match, + type = Expr.order[i]; + + if ((match = Expr.leftMatch[type].exec(expr))) { + var left = match[1]; + match.splice(1, 1); + + if (left.substr(left.length - 1) !== "\\") { + match[1] = (match[1] || "").replace(rBackslash, ""); + set = Expr.find[type](match, context, isXML); + + if (set != null) { + expr = expr.replace(Expr.match[type], ""); + break; + } + } + } + } + + if (!set) { + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName("*") : + []; + } + + return {set: set, expr: expr}; + }; + + Sizzle.filter = function (expr, set, inplace, not) { + var match, anyFound, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML(set[0]); + + while (expr && set.length) { + for (var type in Expr.filter) { + if ((match = Expr.leftMatch[type].exec(expr)) != null && match[2]) { + var found, item, + filter = Expr.filter[type], + left = match[1]; + + anyFound = false; + + match.splice(1, 1); + + if (left.substr(left.length - 1) === "\\") { + continue; + } + + if (curLoop === result) { + result = []; + } + + if (Expr.preFilter[type]) { + match = Expr.preFilter[type](match, curLoop, inplace, result, not, isXMLFilter); + + if (!match) { + anyFound = found = true; + + } else if (match === true) { + continue; + } + } + + if (match) { + for (var i = 0; (item = curLoop[i]) != null; i++) { + if (item) { + found = filter(item, match, i, curLoop); + var pass = not ^ !!found; + + if (inplace && found != null) { + if (pass) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if (pass) { + result.push(item); + anyFound = true; + } + } + } + } + + if (found !== undefined) { + if (!inplace) { + curLoop = result; + } + + expr = expr.replace(Expr.match[type], ""); + + if (!anyFound) { + return []; + } + + break; + } + } + } + + // Improper expression + if (expr === old) { + if (anyFound == null) { + Sizzle.error(expr); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; + }; + + Sizzle.error = function (msg) { + throw "Syntax error, unrecognized expression: " + msg; + }; + + var Expr = Sizzle.selectors = { + order: ["ID", "NAME", "TAG"], + + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch: {}, + + attrMap: { + "class": "className", + "for": "htmlFor" + }, + + attrHandle: { + href: function (elem) { + return elem.getAttribute("href"); + }, + type: function (elem) { + return elem.getAttribute("type"); + } + }, + + relative: { + "+": function (checkSet, part) { + var isPartStr = typeof part === "string", + isTag = isPartStr && !rNonWord.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if (isTag) { + part = part.toLowerCase(); + } + + for (var i = 0, l = checkSet.length, elem; i < l; i++) { + if ((elem = checkSet[i])) { + while ((elem = elem.previousSibling) && elem.nodeType !== 1) { + } + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if (isPartStrNotTag) { + Sizzle.filter(part, checkSet, true); + } + }, + + ">": function (checkSet, part) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if (isPartStr && !rNonWord.test(part)) { + part = part.toLowerCase(); + + for (; i < l; i++) { + elem = checkSet[i]; + + if (elem) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for (; i < l; i++) { + elem = checkSet[i]; + + if (elem) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if (isPartStr) { + Sizzle.filter(part, checkSet, true); + } + } + }, + + "": function (checkSet, part, isXML) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if (typeof part === "string" && !rNonWord.test(part)) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + + "~": function (checkSet, part, isXML) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if (typeof part === "string" && !rNonWord.test(part)) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + + find: { + ID: function (match, context, isXML) { + if (typeof context.getElementById !== "undefined" && !isXML) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME: function (match, context) { + if (typeof context.getElementsByName !== "undefined") { + var ret = [], + results = context.getElementsByName(match[1]); + + for (var i = 0, l = results.length; i < l; i++) { + if (results[i].getAttribute("name") === match[1]) { + ret.push(results[i]); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG: function (match, context) { + if (typeof context.getElementsByTagName !== "undefined") { + return context.getElementsByTagName(match[1]); + } + } + }, + preFilter: { + CLASS: function (match, curLoop, inplace, result, not, isXML) { + match = " " + match[1].replace(rBackslash, "") + " "; + + if (isXML) { + return match; + } + + for (var i = 0, elem; (elem = curLoop[i]) != null; i++) { + if (elem) { + if (not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0)) { + if (!inplace) { + result.push(elem); + } + + } else if (inplace) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function (match) { + return match[1].replace(rBackslash, ""); + }, + + TAG: function (match, curLoop) { + return match[1].replace(rBackslash, "").toLowerCase(); + }, + + CHILD: function (match) { + if (match[1] === "nth") { + if (!match[2]) { + Sizzle.error(match[0]); + } + + match[2] = match[2].replace(/^\+|\s*/g, ''); + + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test(match[2]) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + else if (match[2]) { + Sizzle.error(match[0]); + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function (match, curLoop, inplace, result, not, isXML) { + var name = match[1] = match[1].replace(rBackslash, ""); + + if (!isXML && Expr.attrMap[name]) { + match[1] = Expr.attrMap[name]; + } + + // Handle if an un-quoted value was used + match[4] = ( match[4] || match[5] || "" ).replace(rBackslash, ""); + + if (match[2] === "~=") { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO: function (match, curLoop, inplace, result, not) { + if (match[1] === "not") { + // If we're dealing with a complex expression, or a simple one + if (( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3])) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if (!inplace) { + result.push.apply(result, ret); + } + + return false; + } + + } else if (Expr.match.POS.test(match[0]) || Expr.match.CHILD.test(match[0])) { + return true; + } + + return match; + }, + + POS: function (match) { + match.unshift(true); + + return match; + } + }, + + filters: { + enabled: function (elem) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled: function (elem) { + return elem.disabled === true; + }, + + checked: function (elem) { + return elem.checked === true; + }, + + selected: function (elem) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if (elem.parentNode) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + parent: function (elem) { + return !!elem.firstChild; + }, + + empty: function (elem) { + return !elem.firstChild; + }, + + has: function (elem, i, match) { + return !!Sizzle(match[3], elem).length; + }, + + header: function (elem) { + return (/h\d/i).test(elem.nodeName); + }, + + text: function (elem) { + var attr = elem.getAttribute("type"), type = elem.type; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); + }, + + radio: function (elem) { + return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; + }, + + checkbox: function (elem) { + return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; + }, + + file: function (elem) { + return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; + }, + + password: function (elem) { + return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; + }, + + submit: function (elem) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "submit" === elem.type; + }, + + image: function (elem) { + return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; + }, + + reset: function (elem) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "reset" === elem.type; + }, + + button: function (elem) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && "button" === elem.type || name === "button"; + }, + + input: function (elem) { + return (/input|select|textarea|button/i).test(elem.nodeName); + }, + + focus: function (elem) { + return elem === elem.ownerDocument.activeElement; + } + }, + setFilters: { + first: function (elem, i) { + return i === 0; + }, + + last: function (elem, i, match, array) { + return i === array.length - 1; + }, + + even: function (elem, i) { + return i % 2 === 0; + }, + + odd: function (elem, i) { + return i % 2 === 1; + }, + + lt: function (elem, i, match) { + return i < match[3] - 0; + }, + + gt: function (elem, i, match) { + return i > match[3] - 0; + }, + + nth: function (elem, i, match) { + return match[3] - 0 === i; + }, + + eq: function (elem, i, match) { + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function (elem, match, i, array) { + var name = match[1], + filter = Expr.filters[name]; + + if (filter) { + return filter(elem, i, match, array); + + } else if (name === "contains") { + return (elem.textContent || elem.innerText || Sizzle.getText([elem]) || "").indexOf(match[3]) >= 0; + + } else if (name === "not") { + var not = match[3]; + + for (var j = 0, l = not.length; j < l; j++) { + if (not[j] === elem) { + return false; + } + } + + return true; + + } else { + Sizzle.error(name); + } + }, + + CHILD: function (elem, match) { + var type = match[1], + node = elem; + + switch (type) { + case "only": + case "first": + while ((node = node.previousSibling)) { + if (node.nodeType === 1) { + return false; + } + } + + if (type === "first") { + return true; + } + + node = elem; + + case "last": + while ((node = node.nextSibling)) { + if (node.nodeType === 1) { + return false; + } + } + + return true; + + case "nth": + var first = match[2], + last = match[3]; + + if (first === 1 && last === 0) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if (parent && (parent.sizcache !== doneName || !elem.nodeIndex)) { + var count = 0; + + for (node = parent.firstChild; node; node = node.nextSibling) { + if (node.nodeType === 1) { + node.nodeIndex = ++count; + } + } + + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + + if (first === 0) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID: function (elem, match) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG: function (elem, match) { + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + + CLASS: function (elem, match) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf(match) > -1; + }, + + ATTR: function (elem, match) { + var name = match[1], + result = Expr.attrHandle[name] ? + Expr.attrHandle[name](elem) : + elem[name] != null ? + elem[name] : + elem.getAttribute(name), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS: function (elem, match, i, array) { + var name = match[2], + filter = Expr.setFilters[name]; + + if (filter) { + return filter(elem, i, match, array); + } + } + } + }; + + var origPOS = Expr.match.POS, + fescape = function (all, num) { + return "\\" + (num - 0 + 1); + }; + + for (var type in Expr.match) { + Expr.match[type] = new RegExp(Expr.match[type].source + (/(?![^\[]*\])(?![^\(]*\))/.source)); + Expr.leftMatch[type] = new RegExp(/(^(?:.|\r|\n)*?)/.source + Expr.match[type].source.replace(/\\(\d+)/g, fescape)); + } + + var makeArray = function (array, results) { + array = Array.prototype.slice.call(array, 0); + + if (results) { + results.push.apply(results, array); + return results; + } + + return array; + }; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) + try { + Array.prototype.slice.call(document.documentElement.childNodes, 0)[0].nodeType; + +// Provide a fallback method if it does not work + } catch (e) { + makeArray = function (array, results) { + var i = 0, + ret = results || []; + + if (toString.call(array) === "[object Array]") { + Array.prototype.push.apply(ret, array); + + } else { + if (typeof array.length === "number") { + for (var l = array.length; i < l; i++) { + ret.push(array[i]); + } + + } else { + for (; array[i]; i++) { + ret.push(array[i]); + } + } + } + + return ret; + }; + } + + var sortOrder, siblingCheck; + + if (document.documentElement.compareDocumentPosition) { + sortOrder = function (a, b) { + if (a === b) { + hasDuplicate = true; + return 0; + } + + if (!a.compareDocumentPosition || !b.compareDocumentPosition) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + + } else { + sortOrder = function (a, b) { + // The nodes are identical, we can exit early + if (a === b) { + hasDuplicate = true; + return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if (a.sourceIndex && b.sourceIndex) { + return a.sourceIndex - b.sourceIndex; + } + + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // If the nodes are siblings (or identical) we can do a quick check + if (aup === bup) { + return siblingCheck(a, b); + + // If no parents were found then the nodes are disconnected + } else if (!aup) { + return -1; + + } else if (!bup) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while (cur) { + ap.unshift(cur); + cur = cur.parentNode; + } + + cur = bup; + + while (cur) { + bp.unshift(cur); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for (var i = 0; i < al && i < bl; i++) { + if (ap[i] !== bp[i]) { + return siblingCheck(ap[i], bp[i]); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck(a, bp[i], -1) : + siblingCheck(ap[i], b, 1); + }; + + siblingCheck = function (a, b, ret) { + if (a === b) { + return ret; + } + + var cur = a.nextSibling; + + while (cur) { + if (cur === b) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; + } + +// Utility function for retreiving the text value of an array of DOM nodes + Sizzle.getText = function (elems) { + var ret = "", elem; + + for (var i = 0; elems[i]; i++) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if (elem.nodeType === 3 || elem.nodeType === 4) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if (elem.nodeType !== 8) { + ret += Sizzle.getText(elem.childNodes); + } + } + + return ret; + }; + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) + (function () { + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore(form, root.firstChild); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if (document.getElementById(id)) { + Expr.find.ID = function (match, context, isXML) { + if (typeof context.getElementById !== "undefined" && !isXML) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function (elem, match) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild(form); + + // release memory in IE + root = form = null; + })(); + + (function () { + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild(document.createComment("")); + + // Make sure no comments are found + if (div.getElementsByTagName("*").length > 0) { + Expr.find.TAG = function (match, context) { + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if (match[1] === "*") { + var tmp = []; + + for (var i = 0; results[i]; i++) { + if (results[i].nodeType === 1) { + tmp.push(results[i]); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + + if (div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#") { + + Expr.attrHandle.href = function (elem) { + return elem.getAttribute("href", 2); + }; + } + + // release memory in IE + div = null; + })(); + + if (document.querySelectorAll) { + (function () { + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = "

"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if (div.querySelectorAll && div.querySelectorAll(".TEST").length === 0) { + return; + } + + Sizzle = function (query, context, extra, seed) { + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if (!seed && !Sizzle.isXML(context)) { + // See if we find a selector to speed up + var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(query); + + if (match && (context.nodeType === 1 || context.nodeType === 9)) { + // Speed-up: Sizzle("TAG") + if (match[1]) { + return makeArray(context.getElementsByTagName(query), extra); + + // Speed-up: Sizzle(".CLASS") + } else if (match[2] && Expr.find.CLASS && context.getElementsByClassName) { + return makeArray(context.getElementsByClassName(match[2]), extra); + } + } + + if (context.nodeType === 9) { + // Speed-up: Sizzle("body") + // The body element only exists once, optimize finding it + if (query === "body" && context.body) { + return makeArray([context.body], extra); + + // Speed-up: Sizzle("#ID") + } else if (match && match[3]) { + var elem = context.getElementById(match[3]); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if (elem && elem.parentNode) { + // Handle the case where IE and Opera return items + // by name instead of ID + if (elem.id === match[3]) { + return makeArray([elem], extra); + } + + } else { + return makeArray([], extra); + } + } + + try { + return makeArray(context.querySelectorAll(query), extra); + } catch (qsaError) { + } + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if (context.nodeType === 1 && context.nodeName.toLowerCase() !== "object") { + var oldContext = context, + old = context.getAttribute("id"), + nid = old || id, + hasParent = context.parentNode, + relativeHierarchySelector = /^\s*[+~]/.test(query); + + if (!old) { + context.setAttribute("id", nid); + } else { + nid = nid.replace(/'/g, "\\$&"); + } + if (relativeHierarchySelector && hasParent) { + context = context.parentNode; + } + + try { + if (!relativeHierarchySelector || hasParent) { + return makeArray(context.querySelectorAll("[id='" + nid + "'] " + query), extra); + } + + } catch (pseudoError) { + } finally { + if (!old) { + oldContext.removeAttribute("id"); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for (var prop in oldSizzle) { + Sizzle[prop] = oldSizzle[prop]; + } + + // release memory in IE + div = null; + })(); + } + + (function () { + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; + + if (matches) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9 fails this) + var disconnectedMatch = !matches.call(document.createElement("div"), "div"), + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call(document.documentElement, "[test!='']:sizzle"); + + } catch (pseudoError) { + pseudoWorks = true; + } + + Sizzle.matchesSelector = function (node, expr) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if (!Sizzle.isXML(node)) { + try { + if (pseudoWorks || !Expr.match.PSEUDO.test(expr) && !/!=/.test(expr)) { + var ret = matches.call(node, expr); + + // IE 9's matchesSelector returns false on disconnected nodes + if (ret || !disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9, so check for that + node.document && node.document.nodeType !== 11) { + return ret; + } + } + } catch (e) { + } + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } + })(); + + (function () { + var div = document.createElement("div"); + + div.innerHTML = "
"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if (!div.getElementsByClassName || div.getElementsByClassName("e").length === 0) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if (div.getElementsByClassName("e").length === 1) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function (match, context, isXML) { + if (typeof context.getElementsByClassName !== "undefined" && !isXML) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; + })(); + + function dirNodeCheck(dir, cur, doneName, checkSet, nodeCheck, isXML) { + for (var i = 0, l = checkSet.length; i < l; i++) { + var elem = checkSet[i]; + + if (elem) { + var match = false; + + elem = elem[dir]; + + while (elem) { + if (elem.sizcache === doneName) { + match = checkSet[elem.sizset]; + break; + } + + if (elem.nodeType === 1 && !isXML) { + elem.sizcache = doneName; + elem.sizset = i; + } + + if (elem.nodeName.toLowerCase() === cur) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } + } + + function dirCheck(dir, cur, doneName, checkSet, nodeCheck, isXML) { + for (var i = 0, l = checkSet.length; i < l; i++) { + var elem = checkSet[i]; + + if (elem) { + var match = false; + + elem = elem[dir]; + + while (elem) { + if (elem.sizcache === doneName) { + match = checkSet[elem.sizset]; + break; + } + + if (elem.nodeType === 1) { + if (!isXML) { + elem.sizcache = doneName; + elem.sizset = i; + } + + if (typeof cur !== "string") { + if (elem === cur) { + match = true; + break; + } + + } else if (Sizzle.filter(cur, [elem]).length > 0) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } + } + + if (document.documentElement.contains) { + Sizzle.contains = function (a, b) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + + } else if (document.documentElement.compareDocumentPosition) { + Sizzle.contains = function (a, b) { + return !!(a.compareDocumentPosition(b) & 16); + }; + + } else { + Sizzle.contains = function () { + return false; + }; + } + + Sizzle.isXML = function (elem) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; + }; + + var posProcess = function (selector, context) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ((match = Expr.match.PSEUDO.exec(selector))) { + later += match[0]; + selector = selector.replace(Expr.match.PSEUDO, ""); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for (var i = 0, l = root.length; i < l; i++) { + Sizzle(selector, root[i], tmpSet); + } + + return Sizzle.filter(later, tmpSet); + }; + +// EXPOSE + jQuery.find = Sizzle; + jQuery.expr = Sizzle.selectors; + jQuery.expr[":"] = jQuery.expr.filters; + jQuery.unique = Sizzle.uniqueSort; + jQuery.text = Sizzle.getText; + jQuery.isXMLDoc = Sizzle.isXML; + jQuery.contains = Sizzle.contains; + + + })(); + + + var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.POS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + + jQuery.fn.extend({ + find: function (selector) { + var self = this, + i, l; + + if (typeof selector !== "string") { + return jQuery(selector).filter(function () { + for (i = 0, l = self.length; i < l; i++) { + if (jQuery.contains(self[i], this)) { + return true; + } + } + }); + } + + var ret = this.pushStack("", "find", selector), + length, n, r; + + for (i = 0, l = this.length; i < l; i++) { + length = ret.length; + jQuery.find(selector, this[i], ret); + + if (i > 0) { + // Make sure that the results are unique + for (n = length; n < ret.length; n++) { + for (r = 0; r < length; r++) { + if (ret[r] === ret[n]) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function (target) { + var targets = jQuery(target); + return this.filter(function () { + for (var i = 0, l = targets.length; i < l; i++) { + if (jQuery.contains(this, targets[i])) { + return true; + } + } + }); + }, + + not: function (selector) { + return this.pushStack(winnow(this, selector, false), "not", selector); + }, + + filter: function (selector) { + return this.pushStack(winnow(this, selector, true), "filter", selector); + }, + + is: function (selector) { + return !!selector && ( typeof selector === "string" ? + jQuery.filter(selector, this).length > 0 : + this.filter(selector).length > 0 ); + }, + + closest: function (selectors, context) { + var ret = [], i, l, cur = this[0]; + + // Array + if (jQuery.isArray(selectors)) { + var match, selector, + matches = {}, + level = 1; + + if (cur && selectors.length) { + for (i = 0, l = selectors.length; i < l; i++) { + selector = selectors[i]; + + if (!matches[selector]) { + matches[selector] = POS.test(selector) ? + jQuery(selector, context || this.context) : + selector; + } + } + + while (cur && cur.ownerDocument && cur !== context) { + for (selector in matches) { + match = matches[selector]; + + if (match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match)) { + ret.push({selector: selector, elem: cur, level: level}); + } + } + + cur = cur.parentNode; + level++; + } + } + + return ret; + } + + // String + var pos = POS.test(selectors) || typeof selectors !== "string" ? + jQuery(selectors, context || this.context) : + 0; + + for (i = 0, l = this.length; i < l; i++) { + cur = this[i]; + + while (cur) { + if (pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors)) { + ret.push(cur); + break; + + } else { + cur = cur.parentNode; + if (!cur || !cur.ownerDocument || cur === context || cur.nodeType === 11) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique(ret) : ret; + + return this.pushStack(ret, "closest", selectors); + }, + + // Determine the position of an element within + // the matched set of elements + index: function (elem) { + if (!elem || typeof elem === "string") { + return jQuery.inArray(this[0], + // If it receives a string, the selector is used + // If it receives nothing, the siblings are used + elem ? jQuery(elem) : this.parent().children()); + } + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this); + }, + + add: function (selector, context) { + var set = typeof selector === "string" ? + jQuery(selector, context) : + jQuery.makeArray(selector && selector.nodeType ? [selector] : selector), + all = jQuery.merge(this.get(), set); + + return this.pushStack(isDisconnected(set[0]) || isDisconnected(all[0]) ? + all : + jQuery.unique(all)); + }, + + andSelf: function () { + return this.add(this.prevObject); + } + }); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). + function isDisconnected(node) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; + } + + jQuery.each({ + parent: function (elem) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function (elem) { + return jQuery.dir(elem, "parentNode"); + }, + parentsUntil: function (elem, i, until) { + return jQuery.dir(elem, "parentNode", until); + }, + next: function (elem) { + return jQuery.nth(elem, 2, "nextSibling"); + }, + prev: function (elem) { + return jQuery.nth(elem, 2, "previousSibling"); + }, + nextAll: function (elem) { + return jQuery.dir(elem, "nextSibling"); + }, + prevAll: function (elem) { + return jQuery.dir(elem, "previousSibling"); + }, + nextUntil: function (elem, i, until) { + return jQuery.dir(elem, "nextSibling", until); + }, + prevUntil: function (elem, i, until) { + return jQuery.dir(elem, "previousSibling", until); + }, + siblings: function (elem) { + return jQuery.sibling(elem.parentNode.firstChild, elem); + }, + children: function (elem) { + return jQuery.sibling(elem.firstChild); + }, + contents: function (elem) { + return jQuery.nodeName(elem, "iframe") ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray(elem.childNodes); + } + }, function (name, fn) { + jQuery.fn[name] = function (until, selector) { + var ret = jQuery.map(this, fn, until), + // The variable 'args' was introduced in + // https://github.com/jquery/jquery/commit/52a0238 + // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed. + // http://code.google.com/p/v8/issues/detail?id=1050 + args = slice.call(arguments); + + if (!runtil.test(name)) { + selector = until; + } + + if (selector && typeof selector === "string") { + ret = jQuery.filter(selector, ret); + } + + ret = this.length > 1 && !guaranteedUnique[name] ? jQuery.unique(ret) : ret; + + if ((this.length > 1 || rmultiselector.test(selector)) && rparentsprev.test(name)) { + ret = ret.reverse(); + } + + return this.pushStack(ret, name, args.join(",")); + }; + }); + + jQuery.extend({ + filter: function (expr, elems, not) { + if (not) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function (elem, dir, until) { + var matched = [], + cur = elem[dir]; + + while (cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery(cur).is(until))) { + if (cur.nodeType === 1) { + matched.push(cur); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function (cur, result, dir, elem) { + result = result || 1; + var num = 0; + + for (; cur; cur = cur[dir]) { + if (cur.nodeType === 1 && ++num === result) { + break; + } + } + + return cur; + }, + + sibling: function (n, elem) { + var r = []; + + for (; n; n = n.nextSibling) { + if (n.nodeType === 1 && n !== elem) { + r.push(n); + } + } + + return r; + } + }); + +// Implement the identical functionality for filter and not + function winnow(elements, qualifier, keep) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if (jQuery.isFunction(qualifier)) { + return jQuery.grep(elements, function (elem, i) { + var retVal = !!qualifier.call(elem, i, elem); + return retVal === keep; + }); + + } else if (qualifier.nodeType) { + return jQuery.grep(elements, function (elem, i) { + return (elem === qualifier) === keep; + }); + + } else if (typeof qualifier === "string") { + var filtered = jQuery.grep(elements, function (elem) { + return elem.nodeType === 1; + }); + + if (isSimple.test(qualifier)) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter(qualifier, filtered); + } + } + + return jQuery.grep(elements, function (elem, i) { + return (jQuery.inArray(elem, qualifier) >= 0) === keep; + }); + } + + + var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /", ""], + legend: [1, "
", "
"], + thead: [1, "", "
"], + tr: [2, "", "
"], + td: [3, "", "
"], + col: [2, "", "
"], + area: [1, "", ""], + _default: [0, "", ""] + }; + + wrapMap.optgroup = wrapMap.option; + wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; + wrapMap.th = wrapMap.td; + +// IE can't serialize and - - - " type="image/x-icon" /> - - -
-
- -
diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/serviceErrorSsoView.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/serviceErrorSsoView.jsp deleted file mode 100644 index 6a352a40ba5c..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/serviceErrorSsoView.jsp +++ /dev/null @@ -1,14 +0,0 @@ -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> -<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> - - - - - - -
-

-

-
- \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/serviceErrorView.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/serviceErrorView.jsp deleted file mode 100644 index 7a3bd64dc871..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/serviceErrorView.jsp +++ /dev/null @@ -1,6 +0,0 @@ - -
-

-

-
- \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/errors.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/errors.jsp deleted file mode 100644 index 2651b9a5384b..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/errors.jsp +++ /dev/null @@ -1,9 +0,0 @@ - -
-

CAS is Unavailable

- -

- There was an error trying to complete your request. Please notify your support desk or try again. -

-
- diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casProxyFailureView.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casProxyFailureView.jsp deleted file mode 100644 index bdbe2e96d881..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casProxyFailureView.jsp +++ /dev/null @@ -1,5 +0,0 @@ -<%@ page session="false" contentType="text/plain" %><%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> - - ${fn:escapeXml(description)} - - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casProxySuccessView.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casProxySuccessView.jsp deleted file mode 100644 index 03b0ec3f278d..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casProxySuccessView.jsp +++ /dev/null @@ -1,5 +0,0 @@ -<%@ page session="false" contentType="text/plain" %><%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> - - ${fn:escapeXml(ticket)} - - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationFailure.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationFailure.jsp deleted file mode 100644 index a8eb7ea4c2c9..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationFailure.jsp +++ /dev/null @@ -1,5 +0,0 @@ -<%@ page session="false" contentType="text/plain" %><%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> - - ${fn:escapeXml(description)} - - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp deleted file mode 100644 index 5b9f2292646b..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp +++ /dev/null @@ -1,15 +0,0 @@ -<%@ page session="false" %><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - ${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)} - - ${pgtIou} - - - - - ${fn:escapeXml(proxy.principal.id)} - - - - - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/casPostResponseView.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/casPostResponseView.jsp deleted file mode 100644 index 3c048e102a6c..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/casPostResponseView.jsp +++ /dev/null @@ -1,17 +0,0 @@ -<%@ page language="java" session="false" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - - -
" method="post"> -
- - - -
- -
- - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdServiceFailureView.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdServiceFailureView.jsp deleted file mode 100644 index 802abc755beb..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdServiceFailureView.jsp +++ /dev/null @@ -1,2 +0,0 @@ -openid.mode:id_res -is_valid:false diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdServiceSuccessView.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdServiceSuccessView.jsp deleted file mode 100644 index ba52b15e2415..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdServiceSuccessView.jsp +++ /dev/null @@ -1,2 +0,0 @@ -openid.mode:id_res -is_valid:true diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/user.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/user.jsp deleted file mode 100644 index 66977cfae6ee..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/user.jsp +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/add.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/add.jsp deleted file mode 100644 index b907327d8ba5..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/add.jsp +++ /dev/null @@ -1,93 +0,0 @@ -<%@include file="includes/top.jsp"%> - - - - -
${successMessage}
-
- - -
- -
-
-
-
-

- - - - -
-
- - - - - -
-
-
- - - - - - -
-
- - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - - - - - - - - - - -
-
- -
-
-
- or -
-
-<%@include file="includes/bottom.jsp" %> \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/includes/bottom.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/includes/bottom.jsp deleted file mode 100644 index 9665e7bfe21d..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/includes/bottom.jsp +++ /dev/null @@ -1,21 +0,0 @@ -
- - - - - - - diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/includes/top.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/includes/top.jsp deleted file mode 100644 index 22dd7a0c7f65..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/includes/top.jsp +++ /dev/null @@ -1,89 +0,0 @@ -<%@ page language="java" session="false" %> -<%@ page pageEncoding="UTF-8" %> -<%@ page contentType="text/html; charset=UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> -<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> -<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - - - - <spring:message code="${pageTitle}" text="Logged Out" /> - - " type="text/css" media="screen" /> - - - - - - - - - - - - - -
-

diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/logout.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/logout.jsp deleted file mode 100644 index f27b88905cc9..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/logout.jsp +++ /dev/null @@ -1,6 +0,0 @@ - -
-

-

-
- \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/manage.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/manage.jsp deleted file mode 100644 index effe529c7605..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/manage.jsp +++ /dev/null @@ -1,55 +0,0 @@ -<%@include file="includes/top.jsp"%> - - -

-
- - -
-
- - - - - - - - - - - -
  
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
  
${service.name}${fn:length(service.serviceId) < 50 ? service.serviceId : fn:substring(service.serviceId, 0, 50)}${service.enabled ? 'Enabled' : 'Disabled'}${service.allowedToProxy ? 'Allowed to Proxy' : 'Not Allowed to Proxy'}${service.ssoEnabled ? 'SSO Enabled' : 'SSO Disabled'}
-
-
-<%@include file="includes/bottom.jsp" %> \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/viewStatistics.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/viewStatistics.jsp deleted file mode 100644 index a22f46eaadc4..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/viewStatistics.jsp +++ /dev/null @@ -1,81 +0,0 @@ -<%@include file="includes/top.jsp"%> -

Runtime Statistics

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PropertyValue
Server${serverIpAddress} (${serverHostName})
CAS Ticket Suffix${casTicketSuffix}
Server Start Time${startTime}
Uptime${upTime}
Memory ${freeMemory} MB free ${totalMemory} MB total
Maximum Memory${maxMemory} MB
Available Processors${availableProcessors}
- -

- -

Ticket Registry Statistics

- - - - - - - - - - - - - - - - - - - - - - - - - -
PropertyValue
Unexpired TGTs${unexpiredTgts}
Unexpired STs${unexpiredSts}
Expired TGTs${expiredTgts}
Expired STs${expiredSts}
- -

Performance Statistics

- - -

${appender.name}

- -${appender.name} - -
- -<%@include file="includes/bottom.jsp" %> \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/web.xml b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 93dbca8bb19e..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,240 +0,0 @@ - - - Central Authentication System (CAS) 3.0 - - contextConfigLocation - - /WEB-INF/spring-configuration/*.xml - /WEB-INF/deployerConfigContext.xml - - - - - - - - log4jConfigLocation - classpath:log4j.xml - - - - log4jExposeWebAppRoot - false - - - - CAS Client Info Logging Filter - com.github.inspektr.common.web.ClientInfoThreadLocalFilter - - - - springSecurityFilterChain - org.springframework.web.filter.DelegatingFilterProxy - - - - springSecurityFilterChain - /services/* - - - - CAS Client Info Logging Filter - /* - - - - - - - org.springframework.web.util.Log4jConfigListener - - - - - - - org.jasig.cas.web.init.SafeContextLoaderListener - - - - - - cas - - org.jasig.cas.web.init.SafeDispatcherServlet - - - publishContext - false - - 1 - - - - cas - /login - - - - cas - /logout - - - - cas - /validate - - - - cas - /serviceValidate - - - - cas - /samlValidate - - - - cas - /proxy - - - - cas - /proxyValidate - - - - cas - /CentralAuthenticationService - - - - cas - /services/add.html - - - - cas - /services/viewStatistics.html - - - - - cas - /services/logout.html - - - - cas - /services/loggedOut.html - - - - cas - /services/manage.html - - - - cas - /services/edit.html - - - - cas - /openid/* - - - - cas - /services/deleteRegisteredService.html - - - - cas - /authorizationFailure.html - - - - cas - /403.html - - - - - 5 - - - - org.springframework.context.ApplicationContextException - /WEB-INF/view/jsp/brokenContext.jsp - - - - 500 - /WEB-INF/view/jsp/errors.jsp - - - - 404 - / - - - - 403 - /403.html - - - - index.jsp - - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/cas.css b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/cas.css deleted file mode 100644 index dfa366b869c9..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/cas.css +++ /dev/null @@ -1,94 +0,0 @@ -#cas form {display:block; margin:18px 0 0; padding:0; width:100%; background:#fff;} - #cas fieldset {border:0; margin:0; padding:0; float:left; clear:none; width:auto;} - #cas fieldset legend {display:none;} - - #cas #login {float:left; margin:0 -296px 0 0; padding:15px; width:258px; min-height:15em; background:#eee; position:relative; border:0;} - #cas #login:before {line-height:0.1; font-size:1px; background:transparent url("../images/key-point_tr.gif") no-repeat top right; margin:-15px -15px 0 -15px; height:15px; display:block; content:url("../images/key-point_tl.gif");} - #cas #login:after {display:block; clear:both; padding-top:15px; line-height:0.1; font-size:1px; content:url("../images/key-point_bl.gif"); margin:-15px; height:8px; background:transparent url("../images/key-point_br.gif") scroll no-repeat bottom right;} - #cas #login h2 {border-bottom:1px solid #ddd; padding:3px 0; font:normal 400 17px Georgia, "Times New Roman", Times, serif; color:#333;} - #cas #login label {font-size:11px;} - #cas #login label span.accesskey {text-decoration:underline;} - #cas #login input {letter-spacing:1px;} - #cas #login .check input {position:relative; left:-4px; height:auto;} - #cas #login .check label {float:none; xwidth:auto; line-height:1.8;} - #cas #login .btn-row {position:relative; top:15px; padding-top:10px; border-top:1px solid #ddd;} - * html #cas #login .btn-row {top:5px;} /* IE6 */ - #cas #login .btn-submit {float:none; clear:none; display:inline; letter-spacing:0;} - * html #cas #login .btn-submit {background:#ffd;} - #cas #login .btn-reset {float:none; clear:none; margin-left:5px; border:0; border-left:1px solid #ddd; background:transparent; color:#777; text-transform:lowercase; letter-spacing:0;} - - #cas #sidebar {float:left; margin-left:296px; padding:18px 15px;} - #cas #sidebar h3 {font:normal 400 14px Georgia, "Times New Roman", Times, serif; color:#555; margin:18px 0 0; padding:6px 0 3px;} - #cas #sidebar p {margin:0 0 18px; padding:0; color:#555; font-size:1.1em;} - - #cas #list-languages ul {margin:0; padding:0; line-height:1.5; list-style:none;} - #cas #list-languages ul li {display:inline; padding:0 5px; border-right:1px solid #ccc;} - #cas #list-languages ul li.first {padding-left:0;} - #cas #list-languages ul li.last {padding-right:0; border:0;} - -#cas #footer p {margin:0 0 1em 0; padding:0;} - -/* RESET --------------------------------- */ -/* reset some properties for elements since defaults are not crossbrowser - http://meyerweb.com/eric/thoughts/2007/05/01/reset-reloaded/ */ -html,body,div,span,h1,h2,h3,p,a,img,ul,li,fieldset,form,label,legend {margin:0; padding:0; border:0; outline:0; font-weight:inherit; font-style:inherit; font-size:100%; font-family:inherit; vertical-align:baseline;} -:focus {outline:0;} -ul {list-style:none; font-size:1.1em; padding:0 0 18px 40px;} - -/* browser default font-size is 16px which is too big so we make it 16px x 62.5% = 10px */ - body {font:normal 400 62.5%/1.0 Verdana, sans-serif; min-width:960px; background:#fff; color:#333;} - -/* - - used to clear or contain floats within a non-floated container - - this ruleset is used by UAs that handle :after - not IE - see ie_cas.css for IE fix -http://www.positioniseverything.net/easyclearing.html and http://www.ejeliot.com/blog/59 */ -#header:after, #content:after, #footer:after, .clearfix:after {content:"."; clear:both; display:block; height:0; visibility:hidden;} - - -/* HEADER --------------------------------- */ -#header {position:relative; top:0; left:0; padding-top:52px; background:#fff url(../images/ja-sig-logo.gif) no-repeat scroll 25px 10px;} - #header h1#app-name {clear:both; padding:0 0 0 25px; background:#210f7a; color:#fff; font:normal 400 2.8em/2.5em Georgia,"Times New Roman", serif;} /* d21033 */ - -/* CONTENT --------------------------------- */ -#content {clear:both; padding:1px 0; margin:0 25px 2em;} - #content h2 {margin:0 0 .5em 0; font-size:1.3em; font-weight:400; color:#000; xborder-bottom:1px solid #eee; padding:3px 0; xletter-spacing:-1px;} - #content h3 {font:1em arial, helvetica, sans-serif; font-weight:400;} - - #content p {line-height:1.5; font-size:1.1em; padding:0 0 18px;} - -/* FOOTER --------------------------------- */ -#footer {clear:both; position:relative; margin:0 25px 1em; border-top:1px solid #ccc; padding:0 0 1px 0; background:transparent; color:#999;} - #footer img#logo {position:absolute; right:0; top:0; margin-top:10px;} - #footer div {clear:left; margin:1em 5px .5em; overflow:hidden;} - -/* MESSAGES --------------------------------- */ -.info, .errors, .success {clear:both; margin:18px 0; padding:20px 20px 20px 100px; font-size:10px; line-height:1.5;} -.info {border:1px dotted 008; background:#eff url(../images/info.gif) no-repeat 20px 18px; color:#008;} -.errors {border:1px dotted #d21033; background:#fed url(../images/error.gif) no-repeat 20px 18px; color:#d21033; padding-bottom: 40px;} -.success {border:1px dotted #390; background:#dfa url(../images/confirm.gif) no-repeat 20px 18px; color:#390;} - #content .errors h2, #content .success h2 {font-family:Georgia,"Times New Roman",Times,serif; font-size:18px; line-height:48px; font-weight:400; margin:0 18px 0 0; padding:0;} - #content .success h2 {color: #008 !important;} - #content .errors h2 {color:#b00 !important;} - #content .success h2 {color:#060 !important;} - - -/* static messages */ -#content #msg p {padding:0;} - -/* FORMS --------------------------------- */ - label {cursor:pointer; font-size:1.1em; color:#777;} - input {border-width:1px; font-family:Verdana,sans-serif; font-size:1.1em; color:#000; padding:3px; min-height:1.5em;} - input.btn-submit {border-width:2px;} - - - .fm-v div.row {float:left; margin:0; padding:.5em 0; width:100%;} - .fm-v div.row label {float:left; width:100%; line-height:1.5;} - .fm-v div.row input.btn-submit {display:block; margin:0;} - - -/* highlight errors */ - input.error {background:#FFEFEF; color:#b00;} - -/* mark as required */ - .required {background:#ffd;} - .error {background:#ffefef;} - input.required {border-width:1px;} diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/default-mobile-custom.css b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/default-mobile-custom.css deleted file mode 100644 index 2744fd0a49ae..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/default-mobile-custom.css +++ /dev/null @@ -1,287 +0,0 @@ -/** - * Licensed to Jasig under one or more contributor license - * agreements. See the NOTICE file distributed with this work - * for additional information regarding copyright ownership. - * Jasig licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a - * copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -•ÈÀ/***************************************/ -/* general styles */ -html { - background: -webkit-gradient(linear, left top, right top, - from(#c5ccd3), - to(#cfd5dd), - color-stop(0.80, #c5ccd3), - color-stop(0.80, #cfd5dd)); -} - -.fl-theme-uportal .fl-bevel-black { - text-shadow: rgba(0,0,0,0.35) 0px -1px 0px; -} -.fl-theme-uportal .fl-bevel-white { - text-shadow: rgba(255,255,255,1) 0px 1px 0px; -} - -.fl-theme-uportal a { - -webkit-touch-callout:none; /* prevents iphone popup menu to copy / follow / bookmark a particular link */ - -webkit-tap-highlight-color: rgba(0,0,0,0); - text-decoration:none; - color:#000; -} -/***************************************/ -/* Navigation Bar */ - -body::before { - border-top-color:#ccd6e2; - border-bottom-color:#000; - background-image: -webkit-gradient(linear, left top, left bottom, - from(#B0BCCD), - color-stop(0.5, #889BB3), - color-stop(0.50, #6D84A2), - to(#6D84A2) - ); -} -.fl-theme-uportal .fl-navbar { - color:#fff; - text-shadow: rgba(0,0,0,0.5) 0px -1px 0px; -} - -.fl-theme-uportal .fl-navbar h1 { - color:#fff; -} -.fl-theme-uportal .fl-navbar a { - color: #fff; -} -.fl-theme-uportal .fl-navbar [class*=fl-button] { - background-image: -webkit-gradient(linear, left top, left bottom, - from(#9aafca), - color-stop(0.5, #6d8cb3), - color-stop(0.50, #4b6b90), - to(#4b6b90) - ); - -webkit-border-image:none; -} - -.fl-theme-uportal .fl-navbar [class*=fl-button]:active { - background-image: -webkit-gradient(linear, left top, left bottom, - from(rgba(149, 184, 239,1)), - to(rgba(35,109,229,1)), - color-stop(0.5, rgba(149, 184, 239,1)), - color-stop(0.50, rgba(75,148,244,1)) - ); -} - -.fl-theme-uportal .fl-navbar .fl-backButton { - /* See mobile layout for details */ - -webkit-border-image: url(images/iphone/navbar_back_button_insetShadow.png) 0 15 stretch; - -webkit-mask-box-image: url(images/iphone/backbutton_mask.png) 0 15 stretch; -} - -.fl-theme-uportal .fl-navbar .fl-button-inner { - -webkit-border-image: url(images/iphone/navbar_normal_button_insetShadow.png) 5 5 5 5 stretch; -} - -/***************************************/ -/* iPhone general purpose gel buttons */ -/* effect is applied anywhere but the navbar, which has its own button look */ -.fl-theme-uportal .fl-button { - text-decoration:none; - font-weight:bold; - -webkit-border-image: url(images/iphone/button_bg_insetShadow.png) 10 stretch; -} -.fl-theme-uportal .fl-button.fl-bevel-white { - color:#333333; -} -.fl-theme-uportal .fl-button.fl-bevel-black { - color:#FFFFFF; -} -.fl-theme-uportal .fl-button-white { - background-image: -webkit-gradient(linear, left top, left bottom, - from(rgba(240,240,240,0.25)), - to(rgba(220,220,220,0.75)), - color-stop(0.5, rgba(240,240,240,1)), - color-stop(0.50, rgba(200,200,200,0.8)) - ); -} -.fl-theme-uportal .fl-button-black { - background-image: -webkit-gradient(linear, left top, left bottom, - from(rgba(106,106,106,0.25)), - to(rgba(00,00,00,0.75)), - color-stop(0.5, rgba(130,130,130,1)), - color-stop(0.50, rgba(75,75,75,0.8)) - ); -} -.fl-theme-uportal .fl-button-green { - background-image: -webkit-gradient(linear, left top, left bottom, - from(rgba(120,190,130,0.2)), - to(rgba(50,170,60,0.75)), - color-stop(0.5, rgba(120,190,130,1)), - color-stop(0.50, rgba(0,150,10,0.8)) - ); -} -.fl-theme-uportal .fl-button-blue { - background-image: -webkit-gradient(linear, left top, left bottom, - from(rgba(149, 184, 239,0.2)), - to(rgba(35,109,229,0.75)), - color-stop(0.5, rgba(149, 184, 239,1)), - color-stop(0.50, rgba(75,148,244,0.8)) - ); -} -/***************************************/ -/* iPhone tabs 1: small general purpose content dividers */ - -.fl-theme-uportal .fl-tabs li { - background-image: -webkit-gradient(linear, left top, left bottom, - from(#9aafca), - color-stop(0.5, #6d8cb3), - color-stop(0.50, #4b6b90), - to(#4b6b90) - ); - -webkit-border-image: url(images/iphone/navbar_normal_button_insetShadow.png) 5 5 5 5 stretch; - -webkit-border-left-image: none; - -webkit-background-origin: border; - -webkit-background-clip: border; -} -.fl-theme-uportal .fl-tabs .fl-tabs-active { - background-image: -webkit-gradient(linear, left top, left bottom, - from(rgba(149, 184, 239,1)), - to(rgba(35,109,229,1)), - color-stop(0.5, rgba(149, 184, 239,1)), - color-stop(0.50, rgba(75,148,244,1)) - ); -} - -.fl-theme-uportal .fl-tabs li a, -.fl-theme-uportal .fl-tabs .fl-tabs-active a { - color:#fff; - border-right-color:rgba(255,255,255,0.35); - border-left-color:rgba(0,0,0,0.35); -} - -/*************************************************/ -/* iPhone list menu styles: Ordered lists, Unordererd lists, Thumbnail lists, Icon lists, Definition lists */ -/*************************************************/ -/* Default list system setup */ - -.fl-theme-uportal .fl-listmenu li a { - -webkit-tap-highlight-color: -webkit-gradient(linear, left top, left bottom, from(#4a94f4), to(#236de5)); - background:#fff url(images/iphone/listmenu_arrow.png) no-repeat right 5px; - -} -.fl-theme-uportal [class*=fl-list] > li { - color: #000; - border-color: rgb(169,173,176); - background-color:#fff; -} - -/* A simulation for a:active on the device, requires JS */ -.fl-theme-uportal [class*=fl-list]:not(.fl-list) a:active, -.fl-theme-uportal [class*=fl-list] .fl-link-hilight { - color: #fff; - background: url(images/iphone/listmenu_arrow.png) no-repeat right -25px, - -webkit-gradient(linear, left top, left bottom, from(#4a94f4), to(#236de5)); -} - -.fl-theme-uportal [class*=fl-list] .fl-link-loading { - color: #fff !important; - background: url(images/iphone/listmenu_loader.gif) no-repeat 97% center, - -webkit-gradient(linear, left top, left bottom, from(#4a94f4), to(#236de5)); -} - -.fl-theme-uportal [class*=fl-list]:not(.fl-list) a:active, -.fl-theme-uportal [class*=fl-list] a:active .fl-link-secondary, -.fl-theme-uportal [class*=fl-list] a:active .fl-link-summary, -.fl-theme-uportal [class*=fl-list] .fl-link-loading * { - color: #fff !important; -} - -/* secondary link info behaviour */ -.fl-theme-uportal [class*=fl-list] > li .fl-link-secondary { - color: rgb(50, 79, 133); -} - - /* summary link info ehaviour */ -.fl-theme-uportal [class*=fl-list] > li .fl-link-summary { - color:#999; -} - -.fl-theme-uportal .fl-list a { - color:#4a94f4; -} - -/***************************************/ -/* instructional text (usually embossed too)*/ -.fl-theme-uportal .fl-note { - color:#4C566C; -} - -/****************************/ -/* Collapsing and expanding panels */ -.fl-theme-uportal .fl-panel-autoHeading > *:first-child { - color:#fff; - background: -webkit-gradient(linear, left top, left bottom, from(#999), to(#000)); -} -.fl-theme-uportal .fl-panel-autoHeading > *:first-child:focus { - color:#000; - background: -webkit-gradient(linear, left top, left bottom, from(#FFF), to(#CCC)); -} - -/****************************/ -/* Gloss tint for glossy icons/thumbnails */ - -.fl-theme-uportal .fl-list-glossy > li a::before { - background-image: -webkit-gradient(radial, 50% -15%, 10, 50% -50%, 45, from(rgba(255,255,255,1)), to(rgba(255,255,255,0)), color-stop(90%, rgba(255,255,255,.65))); -} - - -/* ------------------------------------- - Overrides to jqueryui css. ------------------------------------- -*/ - -.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button, -.ui-widget {font-size: 1em; font-family:arial,helvetica,clean,sans-serif;} - -.ui-tabs .ui-tabs-nav li a { - font-size: 0.8em; - padding:0.2em 0.5em; -} - -.ui-tabs .ui-tabs-nav { - padding:0.2em 0.1em 0; -} - -/* jQuery tooltip */ -#tooltip {background-color: #fffbbf; color: #344461; border: thin solid #b1c6f2;} - -/* -*/ - - -#portalWelcome { text-align: right; padding-right: 10px; } -.portlet-content-container { background-color: #fff; padding: 5px; margin: 5px; } - -/* Fluid pager component styles */ -.fl-theme-uportal .fl-pager ul.fl-pager-ui {text-align:right} -.fl-theme-uportal .fl-pager .fl-pager-ui li {list-style-type:none; display:inline; padding-left:5px} -.fl-theme-uportal .fl-pager .fl-pager-ui a.fl-pager-disabled, -.fl-theme-uportal .fl-pager .fl-pager-ui .fl-pager-disabled a { color: #777777; border: 0; text-decoration: none; cursor: default; } -.fl-theme-uportal .fl-pager .fl-pager-ui a.fl-pager-currentPage, -.fl-theme-uportal .fl-pager .fl-pager-ui .fl-pager-currentPage a { color: #000000; border: 0; text-decoration: none; cursor: default;} - -.fl-theme-uportal a { color:#4b6b90; } - -.up-mobile-navigation-container a { color:#000; } \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/fss-framework-1.1.2.css b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/fss-framework-1.1.2.css deleted file mode 100644 index 68481a0fc998..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/fss-framework-1.1.2.css +++ /dev/null @@ -1,611 +0,0 @@ -/* -YUI fonts, reset and base - -Copyright (c) 2008, Yahoo! Inc. All rights reserved. -Code licensed under the BSD License: -http://developer.yahoo.net/yui/license.txt -version: 2.5.2 -*/ -/** - * Percents could work for IE, but for backCompat purposes, we are using keywords. - * x-small is for IE6/7 quirks mode. - */ -body {} -table {font-size:inherit;font:100%;} -/** - * Bump up IE to get to 13px equivalent - */ -pre,code,kbd,samp,tt {font-family:monospace;*font-size:108%;line-height:100%;} - -html{color:#000;background:#FFF;} -body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;} -table{border-collapse:collapse;border-spacing:0;} -fieldset,img{border:0;} -address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;} -li{list-style:none;} -caption,th{text-align:left;} -h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;} -q:before,q:after{content:'';} -abbr,acronym {border:0;font-variant:normal;} -/* to preserve line-height and selector appearance */ -sup {vertical-align:text-top;} -sub {vertical-align:text-bottom;} - -/*because legend doesn't inherit in IE */ -legend{color:#000;} - -/* base.css, part of YUI's CSS Foundation */ -h1 { - /*18px via YUI Fonts CSS foundation*/ - font-size:138.5%; -} -h2 { - /*16px via YUI Fonts CSS foundation*/ - font-size:123.1%; -} -h3 { - /*14px via YUI Fonts CSS foundation*/ - font-size:108%; -} -h1,h2,h3 { - /* top & bottom margin based on font size */ - margin:1em 0; -} -h1,h2,h3,h4,h5,h6,strong { - /*bringing boldness back to headers and the strong element*/ - font-weight:bold; -} -abbr,acronym { - /*indicating to users that more info is available */ - border-bottom:1px dotted #000; - cursor:help; -} -em { - /*bringing italics back to the em element*/ - font-style:italic; -} -blockquote,ul,ol,dl { - /*giving blockquotes and lists room to breath*/ - margin:1em; -} -ol,ul,dl { - /*bringing lists on to the page with breathing room */ - margin-left:2em; -} -ol li { - /*giving OL's LIs generated numbers*/ - list-style: decimal outside; -} -ul li { - /*giving UL's LIs generated disc markers*/ - list-style: disc outside; -} -dl dd { - /*giving UL's LIs generated numbers*/ - margin-left:1em; -} -th,td { - /*borders and padding to make the table readable*/ - border:1px solid #000; - padding:.5em; -} -th { - /*distinguishing table headers from data cells*/ - font-weight:bold; - text-align:center; -} -caption { - /*coordinated margin to match cell's padding*/ - margin-bottom:.5em; - /*centered so it doesn't blend in to other content*/ - text-align:center; -} -p,fieldset,table,pre { - /*so things don't run into each other*/ - margin-bottom:1em; -} -/* setting a consistent width, 160px; - control of type=file still not possible */ -input[type=text],input[type=password],textarea{width:12.25em;*width:11.9em;} -input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;} - -/*************************************************************************************/ -/* Prevent unecessary scrollbars in IE */ -/* Put font data on the HTML element to allow overrides on the body */ -/* IE6 cant zoom text properly, so text size reset is a rough approximation */ -html {overflow:auto; font:13px/1.231 arial,helvetica,clean,sans-serif; *font-size:small;} - -/*to enable resizing for IE*/ -input,textarea,select{ - *font-size:100%; - *font-family:sans-serif; -} - -/* IE rendering fix for extra padding on buttons. Padding can now be set safely */ -input { - *overflow:visible; *padding:0 1em; -} - -/* custom default focus indicator - theme overwrites this */ -:focus { - outline: 2px solid black; -} - - - - - -/* -Generic containers for columns and layouts -============================================= - */ - -/* Container Utilities*/ -.fl-fix {overflow: auto; zoom:1;} -.fl-push {clear:both;} - -/* Container alignment */ - /* IE 6 needs inline display to prevent double margin bug (other browsers ignore it) */ -.fl-force-right {float:right; display:inline;} -.fl-force-left {float:left; display:inline;} -.fl-centered {margin-left:auto; margin-right:auto; display:block;} - -/* Generic container, proxy for other container effects */ -.fl-container {} - -/* Fixed width containers */ -.fl-container-50 {width: 50px;} -.fl-container-100 {width: 100px;} -.fl-container-150 {width: 150px;} -.fl-container-200 {width: 200px;} -.fl-container-250 {width: 250px;} -.fl-container-300 {width: 300px;} -.fl-container-350 {width: 350px;} -.fl-container-400 {width: 400px;} -.fl-container-450 {width: 450px;} -.fl-container-500 {width: 500px;} -.fl-container-550 {width: 550px;} -.fl-container-600 {width: 600px;} -.fl-container-650 {width: 650px;} -.fl-container-700 {width: 700px;} -.fl-container-750 {width: 750px;} -.fl-container-800 {width: 800px;} -.fl-container-850 {width: 850px;} -.fl-container-900 {width: 900px;} -.fl-container-950 {width: 950px;} -.fl-container-1000 {width: 1000px;} - -/* Flex width containers */ -.fl-container-auto {width: auto;} -.fl-container-flex {width: 100%; clear:both;} -.fl-container-flex10 {width: 10%;} -.fl-container-flex20 {width: 20%;} -.fl-container-flex25 {width: 25%;} -.fl-container-flex30 {width: 30%;} -.fl-container-flex33 {width: 33%;} -.fl-container-flex40 {width: 40%;} -.fl-container-flex50 {width: 50%;} -.fl-container-flex60 {width: 60%;} -.fl-container-flex66 {width: 66%;} -.fl-container-flex75 {width: 75%;} - -/* linearizable containers & columns */ -.fl-layout-linear *, -.fl-layout-linear .fl-linearEnabled { - overflow:visible !important; - clear: both !important; - float:none !important; - margin-left:0 !important; - margin-right:0 !important; -} -.fl-layout-linear .fl-container, -.fl-layout-linear .fl-container-100, -.fl-layout-linear .fl-container-150, -.fl-layout-linear .fl-container-200, -.fl-layout-linear .fl-container-250, -.fl-layout-linear .fl-container-300, -.fl-layout-linear .fl-container-400, -.fl-layout-linear .fl-container-750, -.fl-layout-linear .fl-container-950, -.fl-layout-linear .fl-container-auto, -.fl-layout-linear .fl-container-flex25, -.fl-layout-linear .fl-container-flex30, -.fl-layout-linear .fl-container-flex33, -.fl-layout-linear .fl-container-flex50, -.fl-layout-linear .fl-col, -.fl-layout-linear .fl-col-side, -.fl-layout-linear .fl-col-flex, -.fl-layout-linear .fl-col-main, -.fl-layout-linear .fl-col-fixed, -.fl-layout-linear .fl-col-justified {width:100% !important; margin:auto; padding:0 !important;} - -.fl-layout-linear .fl-force-left, -.fl-layout-linear .fl-force-right, -.fl-layout-linear li {display:block !important; float:none !important;} - -.fl-layout-linear .fl-linearEnabled {width:100% !important; /*position:relative;*/ display:block;} /* linearization opt in for special cases */ - -.fl-layout-linear .fl-button-left, -.fl-layout-linear .fl-button-right {padding:1em;} - -/* -Layout Helpers -============================================= -*/ -/* Columns: A quick column grid system */ -/* Flex width columns (containers with margins and padding) */ -.fl-col-justified {float:left; display:inline; overflow:auto; text-align:justify;} /* redundant: text alignment now in fluid.text.css */ - -.fl-col-flex2, .fl-col-flex3, .fl-col-flex4, .fl-col-flex5 {overflow:auto; zoom:1;} -.fl-col {float:left; display:inline;} -.fl-col-flex5 .fl-col {width:18.95%; margin-left:0.25%;margin-right:0.25%; padding-left:0.25%; padding-right:0.25%} -.fl-col-flex4 .fl-col {width:24%; margin-left:0.25%;margin-right:0.25%; padding-left:0.25%; padding-right:0.25%} -.fl-col-flex3 .fl-col {width:32.33%; margin-left:0.25%;margin-right:0.25%; padding-left:0.25%; padding-right:0.25%} -.fl-col-flex2 .fl-col {width:48.85%; margin-left:0.25%;margin-right:0.25%; padding-left:0.25%; padding-right:0.25%} - -/* CHANGE TO LAYOUT not COL since this will become a layout helper */ -.fl-col-mixed, -.fl-col-mixed2, -.fl-col-mixed3 {overflow:auto; zoom:1;} - -/* Old System */ -.fl-col-mixed .fl-col-side {width:200px;} -.fl-col-mixed .fl-col-side, -.fl-col-mixed .fl-col-main {padding:0 10px;} - -.fl-col-mixed2 .fl-col-side {width:200px; padding:0 10px; float:left;} -.fl-col-mixed2 .fl-col-main {margin-left:220px; padding:0 10px;} /* margin goes on whichever side the fixed column goes */ - -.fl-col-mixed3 .fl-col-main {margin:0 220px;} /* margin goes on whichever side the fixed column goes */ - -/* New System, requires fl-force-XX on the fixed column, provides some basic numbers to start with */ -.fl-col-fixed, .fl-col-flex {padding:0 10px;} - -.fl-col-mixed .fl-col-fixed {width:200px; padding:0 10px;} -.fl-col-mixed .fl-col-flex {margin-left:220px; padding:0 10px;} - -.fl-col-mixed-100 .fl-col-fixed {width:100px;} -.fl-col-mixed-100 .fl-col-flex {margin-left:120px;} - -.fl-col-mixed-150 .fl-col-fixed {width:150px;} -.fl-col-mixed-150 .fl-col-flex {margin-left:170px;} - -.fl-col-mixed-200 .fl-col-fixed {width:200px;} -.fl-col-mixed-200 .fl-col-flex {margin-left:220px;} - -.fl-col-mixed-250 .fl-col-fixed {width:250px;} -.fl-col-mixed-250 .fl-col-flex {margin-left:270px;} - -.fl-col-mixed-300 .fl-col-fixed {width:300px;} -.fl-col-mixed-300 .fl-col-flex {margin-left:320px;} - - -/* - * Tabs: a quick tab system - * Dependency: list-based markup ? - */ -.fl-tabs {margin:10px 0 0 0; border-bottom:1px solid #000; text-align:center; padding-bottom:2px;} -.fl-tabs li {list-style-type:none; display:inline;} -/* star hack to get IE 6+7 to behave perfectly */ -.fl-tabs li a {padding:3px 16px 2px; background-color:#fff; margin-left:-5px; *margin-bottom:-6px; zoom:1; border:1px solid #000; color:#999;} -.fl-tabs li a:hover {} -.fl-tabs-center {text-align:center;} -.fl-tabs-left {text-align:left; padding-left:10px;} -.fl-tabs-right {text-align:right; padding-right:15px;} -.fl-tabs .fl-reorderer-dropMarker {padding:0 3px; background-color:#c00;margin:0 5px 0 -5px; zoom:1;} -.fl-tabs .fl-tabs-active a {padding:3px 16px; border-bottom:none; color:#000;} -.fl-tabs-content {padding:5px;} -/* get webkit to behave perfectly - - * unfortunately, Chrome gets caught in this too even though it applied the original padding values fine - */ -@media screen and (-webkit-min-device-pixel-ratio:0){ - .fl-tabs li a {padding:3px 16px 3px;} - .fl-tabs .fl-tabs-active a {padding:3px 16px 4px;} -} - -/* - * Menus: quick horizontal and vertical menu - * Requires list items with anchors - */ -.fl-listmenu {padding:0px; margin:0; border-bottom-width:1px; border-bottom-style:solid;} -.fl-listmenu li {margin:0; padding:0; list-style-type:none; border-width:1px; border-style:solid; border-bottom:none;} -.fl-listmenu a {padding:5px 5px; display:block; zoom:1;} /* list item needs layout (http://www.brunildo.org/test/IEWlispace.php) */ - -.fl-vert-menu { -} -.fl-horz-menu { -} - -/* - * Picture Grid: a quick picture grid layout - * Dependency: list-based markup - */ -ul.fl-grid, .fl-grid ul {padding:0; margin:0;} -.fl-grid li {list-style-type:none; display:inline;} -.fl-grid li {float:left; width:19%; margin:0.5%; height:150px; overflow:hidden; position:relative; display:inline;} -.fl-grid li img {display:block; margin:5px auto;} -.fl-grid li .caption {position:absolute; left:0px; bottom:0px; width:100%; text-align:center; height:1em; padding:3px 0;} - -/* - * Icons: quick accessible icon helper - */ -.fl-icon { - text-indent:-5000px; - overflow:hidden; - cursor:pointer; - display:block; - height:16px; - width:16px; - margin-left:5px; - margin-right:5px; - background-position:center center; - background-repeat:no-repeat; -} -input.fl-icon {padding-left:16px;} - -/* - * Buttons: quick sliding door buttons - * requires a container with a sub container - */ -.fl-button-left {float:left; margin-right:10px; padding:0 0 0 16px; background-position:left center; background-repeat:no-repeat;} -.fl-button-right {float:right; margin-left:10px; padding:0 0 0 16px; background-position:left center; background-repeat:no-repeat;} -.fl-button-inner {float:left; padding:5px 16px 5px 0; cursor:pointer; background-position:right center; background-repeat:no-repeat;} - -/* - * Widgets: modelled after the mycamtools widget model - */ -.fl-widget {padding:5px; margin-bottom:10px;} -.fl-widget .button {margin:0 5px;} -.fl-grabbable .fl-widget-titlebar {background-position:center top; background-repeat:no-repeat; cursor:move;} -.fl-widget .fl-widget-titlebar {} -.fl-widget .fl-widget-titlebar h2 {padding:0; margin:0; font-size:105%;} -.fl-widget .fl-widget-titlebar .fl-button-inner { - font-size:0.8em; - padding-bottom:0.2em; - padding-top:0.2em; -} -.fl-widget .fl-widget-controls {margin:-1.3em 0 1.5em 0;} -.fl-widget .fl-widget-options {margin-top:5px; padding:0px 10px;} -.fl-widget .fl-widget-options ul {margin:0; padding:0; overflow:hidden; zoom:1;} -.fl-widget .fl-widget-options li {list-style-type:none; float:left; display:inline; padding:0 5px 0 5px; margin-left:-5px;} -.fl-widget .fl-widget-options a {margin-right:5px;} -.fl-widget .fl-widget-content {zoom:1; margin:5px 0 0 0; overflow: auto;} -.fl-widget .fl-widget-content ul {} -.fl-widget .empty * {padding-top:10px; margin-left:auto; margin-right:auto; text-align:center;} - - -/* Common widget sub-components */ -.fl-widget .menu {margin:0;} -.fl-widget .toggle {width:32px;} -.fl-widget .on {background-position:left top;} -.fl-widget .off {background-position:left bottom;} - -/* - * Forms and Form Controls - * Common arrangements for form inputs in a list - * Alignment requires list of fl-label elements placed before the control - */ -.fl-controls-left li {list-style-type:none; text-align:left;} -.fl-controls-left .fl-label {float:left; text-align:left; width:50%; margin-right:5px;} - -.fl-controls-right li {list-style-type:none; display:block; text-align:left;} -.fl-controls-right .fl-label {float:left; text-align:right; width:50%; margin-right:5px;} - -.fl-controls-centered li {list-style-type:none; display:block; text-align:left;} -.fl-controls-centered .fl-label {float:left; text-align:center; width:50%; margin-right:5px;} - -/** - * Knockout Background Images - */ -.fl-noBackgroundImages, .fl-noBackgroundImages * { - background-image:none !important; -} -.fl-noBackgroundImages .fl-icon { - text-indent:0 !important; - width:auto !important; - background-color:transparent !important; -} - -/* Progressive Enhancement: JS will reverse the display setup if it is enabled */ -.fl-ProgEnhance-enhanced, /* << syntax breaks conventions and is Deprecated*/ -.fl-progEnhance-enhanced {display:none} - -.fl-ProgEnhance-basic, /* << syntax breaks conventions and is Deprecated*/ -.fl-progEnhance-basic {} - -/* hide text for screen readers. */ -.fl-offScreen-hidden {position:absolute; left:-10000px; top:auto; width:1px; height:1px; overflow:hidden;} - - -.fl-font-size-70, -.fl-font-size-70 body, -.fl-font-size-70 input, -.fl-font-size-70 select, -.fl-font-size-70 textarea {font-size:0.7em !important; line-height:1em !important;} - -.fl-font-size-80, -.fl-font-size-80 body, -.fl-font-size-80 input, -.fl-font-size-80 select, -.fl-font-size-80 textarea {font-size:0.8em !important; line-height:1.1em !important;} - -.fl-font-size-90, -.fl-font-size-90 body, -.fl-font-size-90 input, -.fl-font-size-90 select, -.fl-font-size-90 textarea {font-size:0.9em !important; line-height:1.2em !important;} - -.fl-font-size-100, -.fl-font-size-100 body, -.fl-font-size-100 input, -.fl-font-size-100 select, -.fl-font-size-100 textarea {font-size:1em !important; line-height:1.3em !important;} - -.fl-font-size-110, -.fl-font-size-110 body, -.fl-font-size-110 input, -.fl-font-size-110 select, -.fl-font-size-110 textarea {font-size:1.1em !important; line-height:1.4em !important;} - -.fl-font-size-120, -.fl-font-size-120 body, -.fl-font-size-120 input, -.fl-font-size-120 select, -.fl-font-size-120 textarea {font-size:1.2em !important; line-height:1.5em !important;} - -.fl-font-size-130, -.fl-font-size-130 body, -.fl-font-size-130 input, -.fl-font-size-130 select, -.fl-font-size-130 textarea {font-size:1.3em !important; line-height:1.6em !important;} - -.fl-font-size-140, -.fl-font-size-140 body, -.fl-font-size-140 input, -.fl-font-size-140 select, -.fl-font-size-140 textarea {font-size:1.4em !important; line-height:1.7em !important;} - -.fl-font-size-150, -.fl-font-size-150 body, -.fl-font-size-150 input, -.fl-font-size-150 select, -.fl-font-size-150 textarea {font-size:1.5em !important; line-height:1.8em !important;} - -/* fix for Safari 3 ignoring input font size */ -@media screen and (-webkit-min-device-pixel-ratio:0) { - - [class~='fl-font-size-70'] input[type=submit], - [class~='fl-font-size-70'] input[type=button] {padding:0 1em} - - [class~='fl-font-size-80'] input[type=submit], - [class~='fl-font-size-80'] input[type=button] {font-size:0.8em !important; padding:0 1em} - - [class~='fl-font-size-90'] input[type=submit], - [class~='fl-font-size-90'] input[type=button] {font-size:0.9em !important; padding:0 1em} - - [class~='fl-font-size-100'] input[type=submit], - [class~='fl-font-size-100'] input[type=button] {font-size:1em !important; padding:0 1em} - - [class~='fl-font-size-110'] input[type=submit], - input[type=submit][class~='fl-font-size-110'], - [class~='fl-font-size-110'] input[type=button] {background-color:#fff; font-size:1.1em !important; padding:0 1em} - - [class~='fl-font-size-120'] input[type=submit], - input[type=submit][class~='fl-font-size-120'], - [class~='fl-font-size-120'] input[type=button] {background-color:#fff; font-size:1.2em !important; padding:0 1em} - - [class~='fl-font-size-130'] input[type=submit], - input[type=submit][class~='fl-font-size-130'], - [class~='fl-font-size-130'] input[type=button] {background-color:#fff; font-size:1.3em !important; padding:0 1em} - - [class~='fl-font-size-140'] input[type=submit], - input[type=submit][class~='fl-font-size-140'], - [class~='fl-font-size-140'] input[type=button] {background-color:#fff; font-size:1.4em !important; padding:0 1em} - - [class~='fl-font-size-150'] input[type=submit], - input[type=submit][class~='fl-font-size-150'], - [class~='fl-font-size-150'] input[type=button] {background-color:#fff; font-size:1.5em !important; padding:0 1em} - - [class~='fl-font-serif'] input[type=submit], - [class~='fl-font-sans'] input[type=submit], - [class~='fl-font-monospace'] input[type=submit], - [class~='fl-font-arial'] input[type=submit], - [class~='fl-font-verdana'] input[type=submit], - [class~='fl-font-times'] input[type=submit], - [class~='fl-font-courier'] input[type=submit] {background-color:#fff; padding:0 1em} -} - - -.fl-font-serif, .fl-font-serif * {font-family: Georgia, Times, "Times New Roman", "Book Antiqua", serif !important;} -.fl-font-sans, .fl-font-sans * {font-family: Tahoma, Verdana, Helvetica, sans-serif !important;} -.fl-font-monospace, .fl-font-monospace * {font-family: "Courier New, Courier", monospace !important;} - -.fl-font-arial, .fl-font-arial * {font-family: "Arial" !important;} -.fl-font-verdana, .fl-font-verdana * {font-family: "Verdana" !important;} -.fl-font-times, .fl-font-times * {font-family: Georgia, Times, "Times New Roman", serif !important;} -.fl-font-courier, .fl-font-courier * {font-family: "Courier New", Courier, monospace !important;} - -.fl-text-align-left { - text-align:left; -} -.fl-text-align-right { - text-align:right; -} -.fl-text-align-center { - text-align:center; -} -.fl-text-align-justify { - text-align:justify; -} - -.fl-font-spacing-0, -.fl-font-spacing-0 body, -.fl-font-spacing-0 input, -.fl-font-spacing-0 select, -.fl-font-spacing-0 textarea {letter-spacing:0em} - -.fl-font-spacing-1, -.fl-font-spacing-1 body, -.fl-font-spacing-1 input, -.fl-font-spacing-1 select, -.fl-font-spacing-1 textarea {letter-spacing:0.1em} - -.fl-font-spacing-2, -.fl-font-spacing-2 body, -.fl-font-spacing-2 input, -.fl-font-spacing-2 select, -.fl-font-spacing-2 textarea {letter-spacing:0.2em} - -.fl-font-spacing-3, -.fl-font-spacing-3 body, -.fl-font-spacing-3 input, -.fl-font-spacing-3 select, -.fl-font-spacing-3 textarea {letter-spacing:0.3em} - -.fl-font-spacing-4, -.fl-font-spacing-4 body, -.fl-font-spacing-4 input, -.fl-font-spacing-4 select, -.fl-font-spacing-4 textarea {letter-spacing:0.4em} - -.fl-font-spacing-5, -.fl-font-spacing-5 body, -.fl-font-spacing-5 input, -.fl-font-spacing-5 select, -.fl-font-spacing-5 textarea {letter-spacing:0.5em} - -.fl-font-spacing-6, -.fl-font-spacing-6 body, -.fl-font-spacing-6 input, -.fl-font-spacing-6 select, -.fl-font-spacing-6 textarea {letter-spacing:0.6em} - -/* UI Enhancer "Esier to Find" link options */ -/* First pass strategy: apply classnames directky on the elements of interest, usually via JS */ -.fl-text-aqua {color: aqua !important;} -.fl-text-black {color: black !important;} -.fl-text-blue {color: blue !important;} -.fl-text-fuchsia {color: fuchsia !important;} -.fl-text-gray {color: gray !important;} -.fl-text-green {color: green !important;} -.fl-text-lime {color: lime !important;} -.fl-text-maroon {color: maroon !important;} -.fl-text-navy {color: navy !important;} -.fl-text-olive {color: olive !important;} -.fl-text-purple {color: purple !important;} -.fl-text-red {color: red !important;} -.fl-text-silver {color: silver !important;} -.fl-text-teal {color: teal !important;} -.fl-text-white {color:white !important;} -.fl-text-yellow {color: yellow !important;} - -.fl-text-underline {text-decoration:underline !important;} -.fl-text-bold {font-weight:bold !important;} -.fl-text-larger {font-size:125% !important;} -.fl-input-outline {border:2px solid;} /* leave out color? */ - -.fl-highlight-yellow, .fl-highlight-hover-yellow:hover, .fl-highlight-focus-yellow:focus {background-color:#FF0 !important; background-image:none !important;} -.fl-highlight-green, .fl-highlight-hover-green:hover, .fl-highlight-focus-green:focus {background-color:#0F0 !important; background-image:none !important;} -.fl-highlight-blue, .fl-highlight-hover-blue:hover, .fl-highlight-focus-blue:focus {background-color:#00F !important; background-image:none !important;} - - diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/fss-mobile-iphone-layout.css b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/fss-mobile-iphone-layout.css deleted file mode 100644 index 186cc7a1d0ce..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/fss-mobile-iphone-layout.css +++ /dev/null @@ -1,490 +0,0 @@ -/** - * Licensed to Jasig under one or more contributor license - * agreements. See the NOTICE file distributed with this work - * for additional information regarding copyright ownership. - * Jasig licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a - * copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -:root { - -webkit-background-origin: padding-box; - -webkit-background-clip: content-box; - -webkit-background-size: 10px 1px; -} -body { - margin:0; - padding:0; - font:normal 17px Helvetica, sans-serif; /* Base font rules */ - -webkit-user-select: none; /* prevents child elements content from being selected - desired? */ - -webkit-text-size-adjust:none; /* http://website-engineering.blogspot.com/2009/07/stop-adjusting-text-size-in-iphone-when.html */ -} -h1, h2, h3, h4, h5, h6 {margin:0.25em;} -h1 {font-size:1.5em;} -h2 {font-size:1.1em;} -h3 {font-size:1em;} -h4 {} -h5 {} -h6 {} -img {border:none;} - - -/***************************************/ -/** - * Basic overrides for fss layout - */ - -.fl-icon { - width:30px; - height:30px; - margin-top:-5px; - -webkit-border-radius: 5px; -} -.fl-label { - width:25%; -} - - -/***************************************/ -/* to override any widths for a mobile device. - * This eliminates the width, allowing for device specific screen settings - */ -.fl-panel { - width:auto; -} - -/***************************************/ -/* iPhone general purpose gel buttons */ -/* effect is applied anywhere but the navbar, which has its own button look */ - -.fl-button { - border-width: 10px; - text-align:center; - -webkit-border-radius:10px; - -webkit-background-origin: border; - -webkit-background-clip: border; -} - -/*************************************************/ -/* Navigation Bar - */ -body::before, .fl-navbar { - width:100%; - height:43px; - text-align:center; -} -body::before { - content:" "; - margin-bottom:-43px; - display:block; - border-style:solid; - border-width:1px 0; - -webkit-background-origin: padding-box; - -webkit-background-clip: content-box; -} - -.fl-navbar .fl-table-cell:not(h1) { - width:1px; - white-space:nowrap; -} - -.fl-navbar .fl-table-cell:first-child { - padding-left:5px; -} -.fl-navbar .fl-table-cell:last-child { - padding-right:5px; -} -.fl-navbar h1 { - padding:0; - text-align:center; - font-size:1.17em; - font-weight: bold; -} -.fl-navbar a { - text-decoration:none; - font-size: 0.7em; - font-weight:bold; -} - -.fl-navbar [class*=fl-button] { - -webkit-border-radius:5px; - padding:0; -} - -.fl-navbar .fl-backButton { - padding:10px 0; - /* - * To create the oddly shaped back button with fully customizable CSS colors, there are 2 main things: - * 1) The stencil for the shadow and hilights along the angled edge - * 2) The mask to crop the background along the angled edge - */ - /* Angled effect is just a single border with a transaprent stencil image */ - border-width:0 0 0 15px; - -webkit-border-top-right-radius: 5px; - -webkit-border-bottom-right-radius: 5px; - -webkit-background-origin: border; - -webkit-background-clip: border; - -webkit-mask-repeat: no-repeat; - -webkit-mask-origin: border; - -webkit-mask-clip: border; -} - -.fl-navbar .fl-backButton .fl-button-inner { - margin-left:-3px; /* close tiny gap between angled border + button-inner on Mobile Safari */ - border-left-width:0; -} -.fl-navbar .fl-button-inner { - float:none; - padding:5px; - border-width:5px; - -webkit-background-origin: border; - -webkit-background-clip: border; -} - - - -/*************************************************/ -/* iPhone tabs: general purpose dividers, or fixed to the bottom of the screen - */ -/*************************************************/ -.fl-tabs { - margin:0; - padding:0; - border:none; - text-align:center; -} - -.fl-tabs li { - display:inline-block; - margin-right:-5px; - border-width:5px 0; - text-align:center; -} - -.fl-tabs li:first-child { - border-left-width:5px; - -webkit-border-top-left-radius:5px; - -webkit-border-bottom-left-radius:5px; -} - -.fl-tabs li:last-child { - margin-right:0; - border-right-width:5px; - -webkit-border-top-right-radius:5px; - -webkit-border-bottom-right-radius:5px; -} - -.fl-tabs li.fl-tabs-active a, -.fl-tabs li a { - display:block; - margin:-4px 0; - text-decoration:none; - padding:0.35em 0.5em; - font-weight:bold; - background:none; - border-width:0 1px; - border-style:solid; -} - -.fl-tabs li:last-child a { - border-right:none; -} -.fl-tabs li:first-child a { - border-left:none; -} - -.fl-tabs.fl-tabs-functionBar { - -} - -/*************************************************/ -/* iPhone list menu styles: Ordered lists, Unordererd lists, Thumbnail lists, Icon lists, Definition lists - * By default, all list formats fit to width - * When nested within a fl-panel element, they are indented and therefore contain rounded corners - */ -/*************************************************/ -/* Default list system setup */ -[class*=fl-list] { - border: none; - list-style:none; - margin:0 0 10px; - padding:0; -} - -[class*=fl-list] > li { - display:block; - padding: 12px 0px 12px 12px; - text-decoration: none; - font-weight: bold; - outline: none; - border-style:solid; - border-width:1px 1px 0 1px; - overflow:auto; /* to encapsulate floating elements within */ -} - -[class*=fl-list] > li:last-child { - border-bottom-width:1px; -} - -[class*=fl-list] .fl-link-loading .fl-link-secondary { - display:none; -} - -/* secondary link info behaviour */ -[class*=fl-list] > li .fl-link-secondary { - float:right; - margin-right:25px; - font-weight:normal; - font-size:.9em; -} - - /* summary link info ehaviour */ -[class*=fl-list] > li .fl-link-summary { - display:block; - clear:right; - margin:0 25px 0 0px; - font-weight:normal; - font-size:0.8em; - -} -[class*=fl-list] li .fl-icon ~ .fl-link-summary { - margin-left:30px; /* default size of fl-icon */ -} - -/* icon behaviour */ -[class*=fl-list] li .fl-icon { - float:left; - margin-left:-6px; -} - -.fl-panel [class*=fl-list] > li:first-child, -.fl-panel [class*=fl-list] > li:first-child a { - -webkit-border-top-left-radius: 8px; - -webkit-border-top-right-radius: 8px; -} - -.fl-panel [class*=fl-list] > li:last-child, -.fl-panel [class*=fl-list] > li:last-child a { - -webkit-border-bottom-left-radius: 8px; - -webkit-border-bottom-right-radius: 8px; -} - -/*************************************************/ -/** - * fl-listmenu is now the way to create a link list - * The links create a hotspot over the entire list item, just like how it was by default - */ -.fl-listmenu { -} -.fl-listmenu li { - padding:0; -} -.fl-listmenu li a { - display:block; - padding: 12px 0px 12px 12px; - text-decoration: none; - font-weight: bold; - outline: none; -} - - -/*************************************************/ -/* Thumbnail and Expanded Thumbnails list features */ - -.fl-list-thumbnails > li { - margin-bottom:5px; -} - -.fl-list-thumbnails > li a { - border-bottom-width:1px; - padding-top:6px; - overflow:auto; -} -/* summary behaviour */ -.fl-list-thumbnails > li a > .fl-icon ~ .fl-link-summary { - margin:0 25px -12px 42px; - -} -.fl-list-thumbnails:not(.fl-thumbnails-expanded):not(.fl-list-brief) > li a > .fl-icon ~ .fl-link-summary { - padding-bottom:10px; -} -/* icon behaviour */ -.fl-list-thumbnails > li a > .fl-icon { - width:44px; - height:44px; - margin:-6px 10px -12px -12px; - -webkit-border-radius:0; -} -.fl-panel .fl-list-thumbnails > li a { - -webkit-border-radius:8px; -} -.fl-panel .fl-list-thumbnails > li a > .fl-icon { - -webkit-border-bottom-left-radius: 8px; - -webkit-border-top-left-radius: 8px; -} -.fl-list-thumbnails.fl-thumbnails-expanded li { - margin:0; -} -.fl-list-thumbnails.fl-thumbnails-expanded > li a { - border-bottom-width:0; - -webkit-border-radius:0; - padding:10px; -} -.fl-list-thumbnails.fl-thumbnails-expanded > li a > .fl-icon { - width:60px; - height:60px; - margin:-5px 5px -5px -5px; - -webkit-border-radius:0; -} -.fl-list-thumbnails.fl-thumbnails-expanded > li:last-child a { - border-bottom-width:1px; -} - -.fl-list-thumbnails.fl-thumbnails-expanded > li a > .fl-icon ~ .fl-link-summary { - margin: 0px 0px -5px 60px; -} -.fl-list-thumbnails.fl-thumbnails-expanded > li a > .fl-link-secondary { - padding-top:0; -} -/*************************************************/ -/* Brief lists auto trim summary content + add ellipsis if necessary */ - -.fl-list-brief > li a { - padding-bottom:6px; -} - -.fl-list-brief > li a > .fl-link-summary { - height:1.3em; - overflow:hidden; - text-overflow:ellipsis; - white-space:nowrap; -} - -.fl-list-brief > li a > .fl-link-secondary { - padding-top:6px; -} - -.fl-list-brief > li a > .fl-icon ~ .fl-link-summary { - margin-left:0; -} - -.fl-list-brief.fl-list-thumbnails:not(.fl-thumbnails-expanded) > li a > .fl-icon { - margin:-6px 10px -6px -12px; -} -.fl-list-brief.fl-list-thumbnails:not(.fl-thumbnails-expanded) > li a > .fl-link-secondary { - padding-top:0px; -} - -/*************************************************/ -/* Glossy icons (and thumbnails?) */ - -/* Glossiness for 30x30 icons */ -.fl-list-glossy > li a::before { - position:absolute; - content:" "; - float:left; - width:28px; - height:28px; - margin:-4px 0 0 -5px; - -webkit-border-radius:3px; - -webkit-background-size: 30px 60px; - background-repeat:no-repeat; -} -/* Glossiness for 44x44 thumbnails */ -.fl-list-thumbnails.fl-list-glossy > li a::before { - width:42px; - height:42px; - margin:-11px; - -webkit-border-radius:0px; - -webkit-background-size: 44px 50px; -} -/* Glossiness for 60x60 thumbnails */ -.fl-list-thumbnails.fl-thumbnails-expanded.fl-list-glossy > li a::before { - width:58px; - height:58px; - margin:-4px; - -webkit-border-radius:0px; - -webkit-background-size: 60px 40px; -} - - -/*************************************************/ -/* Content Panels */ -/*************************************************/ - -[class*=fl-panel] { - margin:10px; -} -/* Panel Auto Headings */ -/* The first element found becomes the "heading" */ -.fl-panel-autoHeading > *:first-child { - padding:10px; - margin:0; - -webkit-border-top-left-radius: 8px; - -webkit-border-top-right-radius: 8px; -} -/* The last element found becomes the "content" - list or otherwise */ -.fl-panel-autoHeading > *:last-child { - margin:0; - -webkit-border-radius:0; - -webkit-border-bottom-left-radius:8px; - -webkit-border-bottom-right-radius:8px; -} - -.fl-panel-autoHeading [class*=fl-list] > li { - margin:0; -} -.fl-panel-autoHeading [class*=fl-list] > li:first-child, -.fl-panel-autoHeading [class*=fl-list] > li:first-child a { - -webkit-border-radius:0; -} -.fl-panel-autoHeading [class*=fl-list] > li:first-child:last-child, -.fl-panel-autoHeading [class*=fl-list] > li:first-child:last-child a { - -webkit-border-bottom-left-radius:8px; - -webkit-border-bottom-right-radius:8px; -} -.fl-panel-autoHeading .fl-list-thumbnails > li a { - /*padding:5px;*/ - /*overflow:auto;*/ -} - -/* Collapsing and expanding panels */ -.fl-panel-collapsable { - max-height:900px; - overflow:hidden; - -webkit-border-radius:8px; -} -/***************************/ -.fl-table { - display:table; - border-collapse:collapse; - border:none; -} -.fl-table-row { - display:table-row; - border:none; -} -.fl-table-cell { - display:table-cell; - vertical-align:middle; - border:none; -} - -/***************************/ - - - - - - - diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/ie_cas.css b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/ie_cas.css deleted file mode 100644 index d4e8b34b8746..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/ie_cas.css +++ /dev/null @@ -1,9 +0,0 @@ -/* "* html" hack used to hide styles from IE7 */ -#header, #content, #footer, .clearfix, #student .row, #student .col {zoom:1;} /* needs to be seen by IE7 to trigger hasLayout */ - -/* FOOTER --------------------------------- */ -#footer {padding-bottom:1em;} - -* html #sidebar {display:inline;} /* double margin on float bug */ - -* html input.btn-submit {padding:2px .25em; width:0; overflow:visible;} /* extra width fix */ \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/backbutton_mask.png b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/backbutton_mask.png deleted file mode 100644 index 5191bd0f2271..000000000000 Binary files a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/backbutton_mask.png and /dev/null differ diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/button_bg_insetShadow.png b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/button_bg_insetShadow.png deleted file mode 100644 index 1662147b9ec0..000000000000 Binary files a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/button_bg_insetShadow.png and /dev/null differ diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/listmenu_arrow.png b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/listmenu_arrow.png deleted file mode 100644 index 8a4e37f127ab..000000000000 Binary files a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/listmenu_arrow.png and /dev/null differ diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/listmenu_loader.gif b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/listmenu_loader.gif deleted file mode 100644 index 95294c93d3d6..000000000000 Binary files a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/listmenu_loader.gif and /dev/null differ diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/listmenu_loader.png b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/listmenu_loader.png deleted file mode 100644 index 5ce9ec2ff94e..000000000000 Binary files a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/listmenu_loader.png and /dev/null differ diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/navbar_back_button_insetShadow.png b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/navbar_back_button_insetShadow.png deleted file mode 100644 index 6179fbaf1512..000000000000 Binary files a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/navbar_back_button_insetShadow.png and /dev/null differ diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/navbar_normal_button_insetShadow.png b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/navbar_normal_button_insetShadow.png deleted file mode 100644 index 305ea1bbdaef..000000000000 Binary files a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/images/iphone/navbar_normal_button_insetShadow.png and /dev/null differ diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/services/cas.css b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/services/cas.css deleted file mode 100644 index e59a5c550d57..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/services/cas.css +++ /dev/null @@ -1,392 +0,0 @@ -/* $Id$ */ -/* GLOBAL/UTILITIES - --------------------------------- */ -/* reset margins and padding for all elements since defaults are not crossbrowser */ -* { - margin:0; - padding:0; - font-size:1em; -} -/* browser default font-size is 16px which is too big so we make it 16px x 62.5% = 10px */ -body { - font:normal 62.5%/1 Verdana,Arial,Helvetica,sans-serif; - min-width:992px; -} - -form { - float:left; - margin:0 1%; - padding:0; - width:77%; - } - -/* general positioning styles */ -.ac {text-align:center !important;} - -/* HEADER - --------------------------------- */ -#header #nav-system { - float:right; - padding:0; -} -#header #nav-system ul { - list-style:none; - margin:0; - padding:0; -} -#header #nav-system li { - float:left; - margin:0; - padding:0; -} -#header #nav-system li a { - float:left; - margin:0 0 0 1px; - padding:2px 10px; - font:normal 1.1em/1.5 Verdana,Arial,Helvetica,sans-serif; - text-decoration:none; - background-color:#323265; - color:#fff; - white-space:nowrap; -} -#header #nav-system li a:hover, -#header #nav-system li a:focus { - background-color:#fff; - color:#323265; -} - -#header p#tagline { - padding:0 0 2px 3px; - background: #323265; - color:#fff; - font-size:1.2em; - line-height:1.6; -} -#header h1#app-name { - clear:both; - padding:0 0 0 15px; - background:#323265; - color:#fff; - font:2.4em/2em Arial,Helvetica,sans-serif; -} -/* MAIN MENU - --------------------------------- */ -#nav-main { - float: left; - width: 100%; - background: #999; - font-size: 1.1em; - line-height: normal; - padding: 0; - xmargin:0 0 1.5em 0; - color:#eee; -} -#nav-main ul { - margin: 0; - padding: 0; - list-style: none; - line-height: 1.4em; -} -#nav-main li { - display: inline; - margin: 0; - padding: 0; -} -#nav-main a { - float: left; - margin: 0; - padding: 0; - text-decoration: none; -} -#nav-main a span { - float:left; - display:block; - padding:5px 10px; - background:transparent; - color:#eee; -} -/* Hide from IE5Mac only \*/ -#nav-main a span {float:none;} -/* End hack */ -#nav-main a:hover {background:#eee;} -#nav-main a:hover span { - background:#eee; - color:#333; -} - -.highlightBottom td {background:#FFEFF3; color:#666; font-weight:400;} -.highlightBottom td a {display:inline; background:#fff; padding:5px 10px; color:#666; font-weight: normal;} -.highlightBottom a:hover {background:#b00; color:#fff;} - - -/* CONTENT - --------------------------------- */ -#content { - clear:both; - width:auto; - padding:1px 0; - margin:0 2% 2em; -} -#content h1 { - margin:15px 0; - font:normal 2.2em "Times New Roman",serif; - color:#333; - background:transparent; - text-transform:capitalize; -} -#content p {margin:1em 0;} -/* FOOTER ----------------------------------------------------------- */ -#footer { - color:#999; - background:transparent; - clear:both; - margin:0 2% 2em; - padding:0 0 1px 0; - border-top:1px solid #ccc; - position:relative; -} - -#footer div { - margin:1em 5px .5em; - clear:left; - overflow:hidden; -} -#footer h4 { - font:normal 1em/1.2 Verdana,Arial,Helvetica,sans-serif; - clear:left; - margin:0; - padding:0; - float:left; -} -#footer #nav-campus-sites { - list-style:none; - float:left; - margin:0 0 0 5px; - padding:0; -} -#footer #nav-campus-sites li { - display:inline; - padding:0; - margin:0; - font:normal 1em/1.2 Verdana,Arial,Helvetica,sans-serif; -} -#footer #nav-campus-sites li:before {} /* content: " | " */ -#footer #nav-campus-sites li:first-child:before {} -/* All IE browsers */ -* html #footer {height:1px;} -* html #footer #nav-campus-sites { - padding:0 0.4em 0 0; - margin:0; -} -/* Win IE browsers - hide from Mac IE\*/ -* html #footer #nav-campus-sites {height:1px; } -* html #footer #nav-campus-sites li { - display:block; - float:left; -} -/* End hide from Mac IE 5 */ -* html #footer #nav-campus-sites li:first-child {border-left:0px none;} -/* -_______________________________ ---- CONTENT FRAGMENTS --- -_______________________________ -*/ -/* MESSAGES - --------------------------------- */ -.errors, .success { - clear:both; - padding:20px 20px 20px 85px; /* bg */ - margin:0 0 1em; - font-weight:bold; - font-size:1.3em; - line-height:1.5; -} -.success { - border:1px dotted #390; - color:#390; - background:#dfa url('../../images/services/success.gif') no-repeat 2em 50%; -} - -.errors { - border:1px dotted #b00; - color:#e71708; /* bg */ - background:#fed url('../../images/services/error.gif') no-repeat 2em 50%; -} - -/* FORMS - --------------------------------- */ -fieldset { - border-left:0px solid #ddd; border-right:0px solid #ddd; border-top:1px solid #ddd; border-bottom:0px solid #ddd; margin:2em 0; padding:10px; -} - -legend { - margin:1em 5px; - color:#b00; - font-size:1.1em; - font-weight:bold; - text-transform:uppercase; -} -label { - cursor:pointer; - font-size:1em; - color:#666; -} -input, select, textarea, option {font:normal 1.1em sans-serif;} -input, textarea {padding:0px 2px;} -select option {margin:auto .5em 0 0;} -/* vertically aligned form*/ -input.check { margin:0 0 0 .5em; width:13px; height:13px; vertical-align:middle;} - -/* highlight errors */ -.required {background:#ff9;} - -.formError { -background: #fafafa url('../../images/services/alert2.gif') no-repeat 0 50%; -/* bg */ -color:#b00; /* bg */ -margin-left:.5em; /* bg */ -font-size:1.4em; -line-height:20px; padding-left:24px; padding-right:0; padding-top:0; padding-bottom:0 -} - - -/* TABLES - --------------------------------- */ -/* table row highlighting (does not work in IE - JavaScript workaround) */ -table.highlight tr:hover td, table.highlight tr.over td {background:#ffc !important;} -table.highlight tr.highlightBottom:hover td {background: #ffeff3 !important;} - -table { - border:0px none; - border-collapse:collapse; - empty-cells:show; - background-color:#fff; - font-size:1.1em - } - -.large {width:100%;} - -th { - background:#eee; - color:#666; - padding:3px 5px; - text-align:left; - font-weight:normal; - line-height: 24px; - } - -tr.added { - background-color: #ff3; -} - -td { - padding:3px 5px; - border-bottom:1px solid #eee; - height:38px; - } - -td a { - padding:10px 0pt 10px 35px; - text-decoration: none; - display:inline; - line-height:32px; - color:#c1c1c0; - font-weight:400; -} - -.add { - min-width:952px; - line-height:32px; - height:32px; - border-top: 5px solid #eee; - color:#000; - width:100%; - text-indent:5px; - padding-top: 5px; - font-size:1.1em; -} - -.add a {background: url('../../images/services/add_service.gif') no-repeat left center; - text-decoration: none; - display: inline; - line-height:32px; - color:#c1c1c0; - font-weight:900; padding-left:35px; padding-right:0; padding-top:10px; padding-bottom:10px -} - - -.add a:hover {color:#b00;} -td a.edit {background: url('../../images/services/edit_service.gif') no-repeat left center;} -td a.del {background: url('../../images/services/delete_service.gif') no-repeat left center;} -td a:hover {color:#b00;} - - - p.instructions { - margin:1em 0; - font-size:1.2em; - background:url('../../images/services/info.gif') no-repeat left center;; padding-left:2em; padding-right:0; padding-top:5px; padding-bottom:5px - } - - fieldset { - padding:15px 10px; - background-color:#fafafa; - margin:20px 0; - position:relative; - border:1px solid #ddd; - } - - legend { - padding:2px 5px; - color:#b00; - font-size:1.3em; - font-weight:900; - background:#fff; - } - - label.preField, .label { - display:block; - width:8em; - float:left; - font-size:1.1em; - color:#666; /* + bg */ - line-height:20px; /* + bg */ - } - - label.postField { - margin-right:1em; - font-size:1.1em; - vertical-align:middle; /*- bg */ - line-height:20px; /* + bg */ - } - - .check {border:0px none;} - - input, - textarea, - select { - border:1px solid #ccc; - border-color:#999 #eee #eee #999; - padding:2px; - margin-left:.5em; /* + bg */ - } - - input { - /* margin-left:.5em; - bg */ - font-size:1.1em; - vertical-align:top; - } - - span.oneField { - display:block; - margin:1em 0; /* bg */ - padding:0; - } - -button {font-size:1em;} - -.primaryAction { - padding:.5em; - color:green; - font-weight:900; - } \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/services/ieFix.css b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/services/ieFix.css deleted file mode 100644 index c407a16b3647..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/css/services/ieFix.css +++ /dev/null @@ -1,38 +0,0 @@ -/* IE Pick-a-boo bug fix */ -form {height: expression('1%');} - -/* Fixes Display: Block issue in IE5.5 and 6x */ -div, -form, -fieldset, -#navlist li a, -td a {height:1px;} - -/* Fixes IE problem with fieldset+legend boundaries */ -fieldset {position: expression('relative');} -legend { - position: expression('absolute'); - top: expression('-9px'); /* Fixes IE problem with fieldset+legend boundaries */ - } - -/* double float margin bug */ -form, -#navcontainer {display:inline;} - -/* button width fix */ -button { - overflow:visible; - width:1px; - padding-left:0.5em; - padding-right:0.5em; - font-size:1.1em; - } - -/* miscellaneous */ -input {margin-left:0;} -ol li {line-height:1.0;} -input.check {width:13px; height:13px;} -span a {display:inline-block;} -label.top {margin-left:0;} -label.ieFix {margin-top:.5em;} -fieldset fieldset {padding-bottom:0;} \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/key-point_br.gif b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/key-point_br.gif deleted file mode 100644 index 2262148b72f6..000000000000 Binary files a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/key-point_br.gif and /dev/null differ diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/key-point_tr.gif b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/key-point_tr.gif deleted file mode 100644 index 8ce240790e8a..000000000000 Binary files a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/key-point_tr.gif and /dev/null differ diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/index.jsp b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/index.jsp deleted file mode 100644 index da66678fe8a9..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/index.jsp +++ /dev/null @@ -1,5 +0,0 @@ -<%@ page language="java" session="false" %> -<% -final String queryString = request.getQueryString(); -final String url = request.getContextPath() + "/login" + (queryString != null ? "?" + queryString : ""); -response.sendRedirect(response.encodeURL(url));%> \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/js/cas.js b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/js/cas.js deleted file mode 100644 index 85cc97fd9309..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/js/cas.js +++ /dev/null @@ -1,50 +0,0 @@ -// $Id$ -var editInnerHTML = ""; -var deleteInnerHTML = ""; -var currentRow = null; - -function swapButtonsForConfirm(rowId, serviceId) { - - resetOldValue(); - var editCell = $("#edit"+rowId); - var deleteCell = $("#delete"+rowId); - - var row = $("#row" + rowId); - row.removeClass("over"); - row.addClass("highlightBottom"); - - editInnerHTML = editCell.html(); - deleteInnerHTML = deleteCell.html(); - currentRow = rowId; - - editCell.html("Really?"); - deleteCell.html("Yes No"); -} - -function resetOldValue() { - if (currentRow != null) { - var curRow = $("#row"+currentRow); - curRow.removeClass("over"); - curRow.removeClass("highlightBottom"); - var editCell = $("#edit"+currentRow); - var deleteCell = $("#delete"+currentRow); - - editCell.html(editInnerHTML); - deleteCell.html(deleteInnerHTML); - - editInnerHTML = null; - deleteInnerHTML = null; - currentRow = null; - } -} - -$(document).ready(function(){ - //focus username field - $("input:visible:enabled:first").focus(); - //flash error box - $('#status').animate({ backgroundColor: 'rgb(187,0,0)' }, 30).animate({ backgroundColor: 'rgb(255,238,221)' }, 500); - - //flash success box - $('#msg').animate({ backgroundColor: 'rgb(51,204,0)' }, 30).animate({ backgroundColor: 'rgb(221,255,170)' }, 500); -}); - diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/themes/default/cas.css b/cas-server-3.4.2/cas-server-webapp/src/main/webapp/themes/default/cas.css deleted file mode 100644 index f230acff336b..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/themes/default/cas.css +++ /dev/null @@ -1,343 +0,0 @@ -html,body { - margin:0; - padding:0; - border:0; - background:transparent; - color:#000; - } - -body {font:normal 76% Arial,Helvetica,Verdana,sans-serif;} - -acronym { - border-bottom:1px dotted #ccc; - cursor:help; - } - -a img,#header img { - border:0; - display:block; - } - -.bold {font-weight:bold;} - -.important {color:#b00;} - -.center {text-align:center;} - -.right {text-align:right;} - -.left {text-align:left;} - -/*************************************** HEADER */ -#header{ - margin:0; - padding:0; - width:100%; - clear:both; - background:#b00 url(../images/bgPageHeader.gif) top left repeat scroll; - } - -#header a { - display:block; - margin:0; - padding:0 0 0 3px; - text-decoration:none; - background-color:#b00; - color:#ffc; - font-size:1em; - line-height:19px; - } - -#header a.task { - float:right; - margin:0 0 0 1px; - padding:0 5px 0 20px; - line-height:20px; - text-decoration:none; - color:#ffc; - } - -#header a:hover.task, #header a:focus.task {background-color:#c33 !important;} - -#header a.task#helpBtn { - background:#b00 url(../images/help.gif) 5px 5px no-repeat scroll; -/* display:none; */ - } - -#header a.task#aboutBtn {background:#b00 url(../images/about.gif) 5px 5px no-repeat scroll;} - -#header a.task#menuBtn {background:#b00 url(../images/menu.gif) 5px 5px no-repeat scroll;} - -#header a.task#logoutBtn {background:#b00 url(../images/logout.gif) 5px 5px no-repeat scroll;} - -#header h1 { - margin:0 0 0 5%; - padding:0; - letter-spacing:1px; - background:transparent; - color:#ffc; - font-size:2.5em; - } - -/*************************************** CONTENT */ -#content { - border-top:1px solid #fff; /* REMOVES VERTICAL MARGIN COLLAPSING */ - margin:0 5%; - padding:0; - width:auto !important; - width /**/:100%; - clear:both; - } - -#content .dataset { - margin:30px 0 0 0; - padding:10px; - border:1px solid #eee; - clear:both; - width:auto !important; - width /**/:100%; - } - -#content .dataset h2 { - position:relative; - top:-20px; - left:-20px; - width:350px; - margin:0; - padding:0 0 1px 5px; - border:1px solid #ccc; - background-color:#eee; - color:#066; - font-weight:bold; - font-size:1em; - line-height:1.3em; - } - -#content .dataset .leftCol { - float:left; - width:50%; - min-width:50%; - margin:0; - padding:0; - } - -#appStatus .dataset .rightCol { - float:right; - width:50%; - } - -html>body .clear:after { - content:" "; - display:block; - height:0px; - line-height:0px; - clear:both; - visibility:hidden; /* HIDES THE ELEMENT, BUT STILL TAKES UP SPACE */ - } - -#content p { - line-height:1.6em; - - } - -#content ul { - margin:0; - padding:0; - list-style:none; - } - -#content ul li { - background:url(../images/bullet_orange.gif) 0px 2px no-repeat scroll; - padding-left:20px; - margin-top:.5em; - } - -#content h2 { - margin:0; - padding:0; - font-size:1.8em; - } - -#content h3 { - margin:0; - padding:0; - font-size:1.5em; - color:#333; - } - -#content p.top { - text-align:right; - font:bold 1em Verdana,Helvetica,sans-serif; - } - -/*************************************** FOOTER */ -#footer { - margin:2em 5% 10px 5%; - padding:0; - clear:both; - } - -#footer hr { - margin:0; - padding:0; - height:1px; - background:#ccc; - color:#ccc; - border:0; - } - -#footer p { - margin:0; - padding:0; - font-size:1em; - line-height:1.2em; - color:#999; - } - -#footer p#copyright {margin-top:1em;} - -/*************************************** LINK STYLES */ -#content a:hover, #content a:focus, #content ul li a:hover, #content ul li a:focus, -#menu ul li a:hover, #menu ul li a:focus, -#footer a:hover, #footer a:focus { - background:transparent; - color:#b00; - text-decoration:underline; - } - -/*************************************** TABLE FORMATTING */ -table { - border-right:solid 1px #999; - border-bottom:solid 1px #999; - background-color:#fff; - font-size:1em; - } - -th { - border-left:solid 1px #ccc; - border-top:solid 1px #ccc; - background-color:#eee; - color:#066; - font-weight:normal; - font-size:1em; - vertical-align:middle; - text-align:left; - padding:0 0 0 2px; - } - -td { - border-left:solid 1px #ccc; - border-top:solid 1px #ccc; - font-size:1em; - padding:0 0 0 2px; - } - -/*************************************** FORMS */ -form {display:inline;} - -input:focus, textarea:focus, select:focus {background:#fc3 !important;} - -input.numeric {text-align:right;} - -/*************************************** BUTTON STYLES */ -#content a.button, #content a.button:link, #content a.button:hover, #content a.button:active, #content .button { - text-decoration:none; - background-color:#d0d0d0; - border:1px outset #d0d0d0; - color: #000; - padding:1px 1em; - font-size:1.2em; - line-height:1.2em; - } - -/*************************************** HELP */ -#help { - background-color:#ffc; - border:1px solid #ccc; - margin:0 0 15px 0; - padding:0 0 10px 0; - } - -#help h4 { - margin:0 0 10px 0; - border-bottom:1px solid #ccc; - background-color:#fc3; - color:#b00; - font-size:1em; - font-weight:bold; - line-height:19px; - padding:0 0 0 3px; - } - -#help p { - margin:10px; - padding:0; - } - -/* #help #closeHelp {display:none;} */ - -#help h4 a, #help h4 a:visited { - float:right; - margin:0; - padding:0 5px 0 20px; - text-decoration:none !important; - color:#ffc !important; - font-weight:normal; - line-height:19px; - background:#b00 url(../images/close.gif) 5px 5px no-repeat scroll; - } - -#help h4 a:hover, #help h4 a:focus {background:#c33 url(../images/close.gif) 5px 5px no-repeat scroll !important;} - -fieldset { - border-top:solid 1px #ccc; - border-bottom:0; - border-right:0; - border-left:0; - margin:0; - padding:0; - padding-bottom: 1.5em; - } -form{ - display: inline; - } -.accesskey {text-decoration:underline;} -fieldset div {padding-top:10px;} - -legend {color:#066;} - -input, select,textarea { - font-family:monospace; - font-size:11px; - margin:0; - padding:0; - } - -#content .button { - padding:3px 0px; - font-size:11px; - width:11em; - cursor:pointer; - } - -label { - margin:0 5px 0 0; - font-weight:bold; - cursor:pointer; - } - -.required {background:#ffc;} -.important {color:#b00;font-family:monospace;font-weight:bold;font-size:1.2em;} -.evenrow {background-color:#eee;} - -table .appHeadingRow th { - border-top:5px solid #ccc; - background-color:#666; - color:#fff; - text-align:left; - padding:0 0 0 2px; - } - - -#content .button#add {background:#d0d0d0 url(../images/arrowd.gif) right center no-repeat scroll;} -#content .button#update {background:#d0d0d0 url(../images/update.gif) right center no-repeat scroll;margin-top:10px;} diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/clover/clover.license b/cas-server-3.4.2/cas-server-webapp/src/test/clover/clover.license deleted file mode 100644 index 8506a9d22149..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/clover/clover.license +++ /dev/null @@ -1,166 +0,0 @@ -Product: Clover -License: Open Source License, 0.x, 1.x -Issued: Sun Feb 13 2005 22:45:43 CST -Expiry: Never -Key: aae4c7035b1208e316fa6d684 -Name: Scott Battaglia -Org: JA-SIG Central Authentication Service -Certificate: AAACCG+Ow8B7/zEbxOMqqKwwrdpP+a1COmJGHco7sCNLjHkHnajPF+dQW -Ct12PMy0uml0s9xuus5wKngJ9OFk5PFeh01dzQF66bhXH1bvegLfvja3Kle6BYtDv4LZgE -gk3E0aJN4IbgTn+TgUckSevXDR4KzK77NWJfrVzkxV3/JepYRA9UAbsXHiki9WjMIJfzoZ -unjvtTFd/vTOcyirgfT/dTPrG9PAGAjH+e37E9Xf7HnRSrmxtrGX+wdaBOlZFUIIcVHKT2 -IaGToLZnx7FvfE3rzyQCYtS+r0E+H61c+dANzySy5PEH2JEyL8M9JrwgYJSju1FWhxbXO2 -Gb7y3Diufo80+HWz6xrGzl9IlXRseoXHki8rllk5taXqVv5G3UIsTFzbjjWUDlykn557C2 -D4o9T0xQ/6dVFxak75o0MxP4aXGFMZNg/pCBH9DAU7/CKVKRBPAVx1PJ8vIy41MF4p9Mi1 -qmELNvjn9K9fwpaeiUG9qT8B0pfq/tTAObG2sZf7B1mFbA3YwEqjhNqLdkoca5swrS0DI1 -9OejIVIqjK+bvUZaqUDMxVX7fM6hwRvI9Wd+rwFG+X3wHYNPsZ+Cos8/BNPzIIoOh4SbTr -8vIqWUdPXM4HO26uAAVRKz6FknmwM/GQ7FyJBWgIXgXK51SLn+NcifxO8uywGewHzP00ki -frTPmy0+GrikleWry6BdWkg76hjrjQBalNlSmasi9yp9J8qdzVYvQlOBjS5EKWsvsSpXGY -MIdupkiv4a25aXsgdpGBy4GzcSUDioChq287HBBmYRIMVvp5OYbV3/+ERNhTlCQqb6Ck4g -891l1OJOEoMiDqcbDL8DNftlH4gybEE7zJXQRmmJKyw== -License Agreement: CLOVER VERSION 1 (ONE) SOFTWARE LICENSE AGREEMENT - -1. Licenses and Software - -Cortex eBusiness Pty Ltd, an Australian Proprietary Limited Company -("CENQUA") hereby grants to the purchaser (the "LICENSEE") a limited, -revocable, worldwide, non-exclusive, non-transferable, -non-sublicensable license to use the Clover version 1 (one) software -(the "Software"), including any minor upgrades thereof during the Term -(hereinafter defined) up to, but not including the next major version -of the Software. The licensee shall not, or knowingly allow others to, -reverse engineer, decompile, disassemble, modify, adapt, create -derivative works from or otherwise attempt to derive source code from -the Software provided. And, in accordance with the terms and -conditions of this Software License Agreement (the "Agreement"), the -Software shall be used solely by the licensed users in accordance with -the following edition specific conditions: - -a) Server Edition - -A Server Edition license entitles the Licensee to execute one instance -of Clover Server Edition on one (1) machine for the purposes of -instrumenting source code and generating reports. There are no -limitations on the use of the instrumented source code or generated -reports produced by Server Edition. - -b) Workstation Edition - -A Workstation Edition license entitles the licensee to use Clover -Workstation Edition on one (1) machine by one (1) individual end -user. Workstation Edition does not permit the generation of reports -for distribution. - -c) Team Edition - -A Team Edition license entitles the licensee to use Clover Team -edition on any number of machines solely by the licensed number of -users. Reports generated by Clover Team Edition are strictly for use -only by the licensed number of individual end users. - -2. License Fee - -In exchange for the License(s), the Licensee shall pay to Cenqua a -one-time, up front, non-refundable license fee. At the sole discretion -of Cenqua this fee will be waived for non-commercial -projects. Notwithstanding the Licensee's payment of the License Fee, -Cenqua reserves the right to terminate the License if Cenqua discovers -that the Licensee and/or the Licensee's use of the Software is in -breach of this Agreement. - -3. Proprietary Rights - -Cenqua will retain all right, title and interest in and to the -Software, all copies thereof, and Cenqua website(s), software, and -other intellectual property, including, but not limited to, ownership -of all copyrights, look and feel, trademark rights, design rights, -trade secret rights and any and all other intellectual property and -other proprietary rights therein. The Licensee will not directly or -indirectly obtain or attempt to obtain at any time, any right, title -or interest by registration or otherwise in or to the trademarks, -service marks, copyrights, trade names, symbols, logos or designations -or other intellectual property rights owned or used by Cenqua. All -technical manuals or other information provided by Cenqua to the -Licensee shall be the sole property of Cenqua. - -4. Term and Termination - -Subject to the other provisions hereof, this Agreement shall commence -upon the Licensee's opting into this Agreement and continue until the -Licensee discontinues use of the Software or the Agreement terminates -automatically upon the Licensee's breach of any term or condition of -this Agreement (the "Term"). Upon any such termination, the Licensee -will delete the Software immediately. - -5. Copying & Transfer - -The Licensee may copy the Software for back-up purposes only. The -Licensee may not assign or otherwise transfer the Software to any -third party. - -6. Specific Disclaimer of Warranty and Limitation of Liability - -THE SOFTWARE IS PROVIDED WITHOUT WARRANTY OF ANY KIND. CENQUA -DISCLAIMS ALL WARRANTIES, EXPRESSED OR IMPLIED, INCLUDING BUT NOT -LIMITED TO THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE. CENQUA WILL NOT BE LIABLE FOR ANY DAMAGES -ASSOCIATED WITH THE SOFTWARE, INCLUDING, WITHOUT LIMITATION, ORDINARY, -INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING -BUT NOT LIMITED TO DAMAGES RELATING TO LOST DATA OR LOST PROFITS, EVEN -IF CENQUA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. Warranties and Representations - -Licensee Indemnification. CENQUA agrees to indemnify, defend and hold -the Licensee harmless from and against any and all liabilities, -damages, losses, claims, costs, and expenses (including reasonable -legal fees) arising out of or resulting from the Software or the use -thereof infringing upon, misappropriating or violating any patents, -copyrights, trademarks, or trade secret rights or other proprietary -rights of persons, firms or entities who are not parties to this -Agreement. - -CENQUA Indemnification. The Licensee warrants and represents that the -Licensee's actions with regard to the Software will be in compliance -with all applicable laws; and the Licensee agrees to indemnify, -defend, and hold CENQUA harmless from and against any and all -liabilities, damages, losses, claims, costs, and expenses (including -reasonable legal fees) arising out of or resulting from the -Licensee's failure to observe the use restrictions set forth herein. - -8. Publicity - -The Licensee grants permission for CENQUA to use Licensee's name -solely in customer lists. CENQUA shall not, without prior consent in -writing, use the Licensee's name, or that of its affiliates, in any -form with the specific exception of customer lists. CENQUA agrees to -remove Licensee's name from any and all materials within 7 days if -notified by the Licensee in writing. - -9. Governing Law - -This Agreement shall be governed by the laws of New South Wales, -Australia. - -10. Independent Contractors - -The parties are independent contractors with respect to each other, -and nothing in this Agreement shall be construed as creating an -employer-employee relationship, a partnership, agency relationship or -a joint venture between the parties. - -11. Assignment - -This Agreement is not assignable or transferable by the Licensee. -CENQUA in its sole discretion may transfer a license to a third party -at the written request of the Licensee. - -12. Entire Agreement - -This Agreement constitutes the entire agreement between the parties -concerning the Licensee's use of the Software. This Agreement -supersedes any prior verbal understanding between the parties and any -Licensee purchase order or other ordering document, regardless of -whether such document is received by CENQUA before or after execution -of this Agreement. This Agreement may be amended only in writing by -CENQUA. diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/java/org/jasig/cas/util/AutowiringSchedulerFactoryBeanTests.java b/cas-server-3.4.2/cas-server-webapp/src/test/java/org/jasig/cas/util/AutowiringSchedulerFactoryBeanTests.java deleted file mode 100644 index 28c9f47d160a..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/java/org/jasig/cas/util/AutowiringSchedulerFactoryBeanTests.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2007 The JA-SIG Collaborative. All rights reserved. See license - * distributed with this file and available online at - * http://www.uportal.org/license.html - */ -package org.jasig.cas.util; - -import junit.framework.TestCase; - -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.quartz.Scheduler; - -/** - * Test case for {@link org.jasig.cas.util.AutowiringSchedulerFactoryBean} class. - * - * @author Marvin S. Addison - * @version $Revision: $ $Date: $ - * @since 3.3.3 - * - */ -public class AutowiringSchedulerFactoryBeanTests extends TestCase { - private ApplicationContext context; - - private Scheduler scheduler; - - - protected void setUp() throws Exception { - context = new ClassPathXmlApplicationContext(new String[] { - "applicationContext.xml"}); - - this.scheduler = (Scheduler) context.getBean("autowiringSchedulerFactoryBean"); - this.scheduler.start(); - - } - - public void testAfterPropertiesSet() throws Exception { - assertEquals(1, this.scheduler.getTriggerNames(Scheduler.DEFAULT_GROUP).length); - } -} diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/resources/applicationContext.xml b/cas-server-3.4.2/cas-server-webapp/src/test/resources/applicationContext.xml deleted file mode 100644 index 5904ba337790..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/resources/applicationContext.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - Configuration for the default TicketRegistry which stores the tickets in-memory and cleans them out as specified intervals. - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/build.xml b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/build.xml deleted file mode 100644 index 8c0ef44c2204..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/build.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/includes/config.xml b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/includes/config.xml deleted file mode 100644 index 0033a40d2332..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/includes/config.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/includes/definition.xml b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/includes/definition.xml deleted file mode 100644 index 3da22c510b9c..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/includes/definition.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/logintests.xml b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/logintests.xml deleted file mode 100644 index baad9883e4ab..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/logintests.xml +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - - - - - - -]> - - - &definition; - - - - - - - &config; - - &getLoginFormWithoutService; - &verifyLoginForm; - - - - - &checkBadCredentials; - - - - - &config; - - &getLoginFormWithoutService; - &verifyLoginForm; - - - - - &checkLoginSuccess; - &verifyCookie; - - - - - &config; - - &getLoginFormWithService; - &verifyLoginForm; - - - - &verifyCookie; - &verifyRedirect; - &getLogout; - &getLoginFormWithService; - &verifyLoginForm; - - - - - &config; - - &getLoginFormWithService; - &verifyLoginForm; - - - - &verifyCookie; - &verifyRedirect; - - &verifyRedirect; - - - - - &config; - - &getLoginFormWithService; - &verifyLoginForm; - - - - - &verifyCookie; - &verifyRedirect; - &getLoginFormWithService; - &verifyCookie; - &checkWarnPage; - - &checkWarnPage; - - - - - &config; - - - - - - - - - - - &config; - - &getLoginFormWithService; - &verifyLoginForm; - - - - &verifyCookie; - &verifyRedirect; - - &verifyLoginForm; - - - - &verifyCookie; - &verifyRedirect; - - - - - diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/checkBadCredentials.xml b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/checkBadCredentials.xml deleted file mode 100644 index 03bfb9638e58..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/checkBadCredentials.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/checkLoginSuccess.xml b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/checkLoginSuccess.xml deleted file mode 100644 index 7091d63cbb6d..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/checkLoginSuccess.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/checkWarnPage.xml b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/checkWarnPage.xml deleted file mode 100644 index 0a4f91ee9ca0..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/checkWarnPage.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/extractServiceTicket.xml b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/extractServiceTicket.xml deleted file mode 100644 index a99bdebf5ec1..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/extractServiceTicket.xml +++ /dev/null @@ -1,4 +0,0 @@ - - def m = step.webtestProperties.location; - step.setWebtestProperty('serviceTicket',m.substring(m.indexOf("ST-")),'dynamic'); - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/getLoginFormWithService.xml b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/getLoginFormWithService.xml deleted file mode 100644 index ba3aed978937..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/getLoginFormWithService.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/getLoginFormWithoutService.xml b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/getLoginFormWithoutService.xml deleted file mode 100644 index 9d08cfbe63e6..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/getLoginFormWithoutService.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/getLogout.xml b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/getLogout.xml deleted file mode 100644 index c2a6a8680719..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/getLogout.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/processLogin.xml b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/processLogin.xml deleted file mode 100644 index 9947ee23ca00..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/processLogin.xml +++ /dev/null @@ -1,9 +0,0 @@ -&getLoginFormWithService; -&verifyLoginForm; - - - - -&verifyCookie; -&verifyRedirect; -&extractServiceTicket; \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/verifyCookie.xml b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/verifyCookie.xml deleted file mode 100644 index bdb3a139eeef..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/verifyCookie.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/verifyLoginForm.xml b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/verifyLoginForm.xml deleted file mode 100644 index 4ffe468fdb3e..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/verifyLoginForm.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/verifyRedirect.xml b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/verifyRedirect.xml deleted file mode 100644 index 533431ef53e6..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/modules/verifyRedirect.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/properties/canoo.properties b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/properties/canoo.properties deleted file mode 100644 index a47aecf71ddb..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/properties/canoo.properties +++ /dev/null @@ -1,17 +0,0 @@ -#Option for canoo web test -host=localhost -port=8443 -protocol=https -basepath=cas -haltonfailure=true -haltonerror=true -showhtmlparseroutput=true -autorefresh=true -saveresponse=true -resultpath=${basedir}/../../../target/webtest -resultfile=webtest-raw-report.xml -summary=true - -#Proxy Call Back Test Application -proxyCallBackURL1=https://localhost/proxyCallBackTest1/index.jsp -proxyCallBackURL2=https://localhost/proxyCallBackTest2/index.jsp \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/properties/local.properties b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/properties/local.properties deleted file mode 100644 index 800afa329bfa..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/properties/local.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Place here your WEBTEST_HOME -webtest.home=C:/Program Files/Canoo/ diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/proxyCallBackTest/WEB-INF/web.xml b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/proxyCallBackTest/WEB-INF/web.xml deleted file mode 100644 index abff183123b7..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/proxyCallBackTest/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - Welcome to PGTest - - PGTest - - - diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/proxyCallBackTest/index.jsp b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/proxyCallBackTest/index.jsp deleted file mode 100644 index 9ea327df812c..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/proxyCallBackTest/index.jsp +++ /dev/null @@ -1,9 +0,0 @@ -<% -if( request.getParameter("pgtId") != null ) { - System.out.println("Set PGT : #" + request.getParameter("pgtId") + "#"); - application.setAttribute("pgtId",request.getParameter("pgtId")); -} else { - System.out.println("Get PGT : #" + application.getAttribute("pgtId") + "#"); - out.println("PGT: #" + application.getAttribute("pgtId") + "#"); -} -%> \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/validationtests.xml b/cas-server-3.4.2/cas-server-webapp/src/test/webtest/validationtests.xml deleted file mode 100644 index ae01dbd99743..000000000000 --- a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/validationtests.xml +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - -]> - - - &definition; - - - - - &config; - - &processLogin; - - - - - - - - - &config; - - &processLogin; - - - - - - - - - &config; - - &processLogin; - - - - - - - - - &config; - - &processLogin; - - - - - - - - - &config; - - &processLogin; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - &getLogout; - - - - - - diff --git a/cas-server-3.4.2/etc/docs/CAS+2.0+Protocol+Specification+v1.0.doc b/cas-server-3.4.2/etc/docs/CAS+2.0+Protocol+Specification+v1.0.doc deleted file mode 100644 index c9058611d2ae..000000000000 Binary files a/cas-server-3.4.2/etc/docs/CAS+2.0+Protocol+Specification+v1.0.doc and /dev/null differ diff --git a/cas-server-3.4.2/license.txt b/cas-server-3.4.2/license.txt deleted file mode 100644 index 27ee2c11785b..000000000000 --- a/cas-server-3.4.2/license.txt +++ /dev/null @@ -1,31 +0,0 @@ -License for Use - -Copyright (c) 2007, JA-SIG, Inc. - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither the name of the JA-SIG, Inc. nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/cas-server-3.4.2/notice.txt b/cas-server-3.4.2/notice.txt deleted file mode 100644 index 0219458bbf17..000000000000 --- a/cas-server-3.4.2/notice.txt +++ /dev/null @@ -1,13 +0,0 @@ -The end-user documentation included with a redistribution, if any, -must include the following acknowledgement: - - "This product includes software developed by the JA-SIG Collaborative - (http://www.ja-sig.org/)." - -Alternately, this acknowledgement may appear in the software itself, -if and wherever such third-party acknowledgements normally appear. - -The names "CAS", "Central Authentication Service" and "JA-SIG Central -Authentication Service" must not be used to endorse or promote products -derived from this software without prior written permission. For written -permission, please contact wgthom@rutgers.edu diff --git a/cas-server-3.4.2/pom.xml b/cas-server-3.4.2/pom.xml deleted file mode 100644 index 77cd0948a62f..000000000000 --- a/cas-server-3.4.2/pom.xml +++ /dev/null @@ -1,581 +0,0 @@ - - - org.jasig.parent - jasig-parent - 21 - - 4.0.0 - org.jasig.cas - cas-server - pom - Jasig Central Authentication Service - Parent POM for Jasig CAS Project - 3.4.3.1-SNAPSHOT - http://www.jasig.org/cas/ - 2004 - - - - JA-SIG License for Use - http://www.jasig.org/cas/license - repo - - - - - scm:svn:https://source.jasig.org/cas3/branches/cas-3_4_x_maintenance/cas-server-3.4.2 - scm:svn:https://source.jasig.org/cas3/branches/cas-3_4_x_maintenance/cas-server-3.4.2 - https://source.jasig.org/cas3/branches/cas-3_4_x_maintenance/cas-server-3.4.2 - - - - - - ${basedir}/src/test/resources - - - - - org.apache.maven.plugins - maven-enforcer-plugin - - - enforce-versions - - enforce - - - - - 2.0.9 - - - 1.5 - - - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - **/*Tests.java - - - **/Abstract*.java - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.5 - 1.5 - - - - org.apache.maven.plugins - maven-jar-plugin - - - - true - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 2.2-beta-3 - - - ${basedir}/assembly.xml - - - - - org.codehaus.mojo - aspectj-maven-plugin - 1.3 - - - - compile - - - - - 1.5 - 1.5 - - - - - org.apache.maven.plugins - maven-release-plugin - 2.0 - - forked-path - - - - - - - - junit - junit - ${junit.version} - test - - - - org.springframework - spring-test - ${spring.version} - test - - - - javax.servlet - servlet-api - provided - - - - org.aspectj - aspectjrt - compile - ${aspectj.version} - - - - org.aspectj - aspectjweaver - compile - ${aspectj.version} - - - - javax.validation - validation-api - ${javax.validation.version} - compile - - - - org.slf4j - slf4j-api - ${slf4j.version} - compile - - - - org.slf4j - slf4j-log4j12 - ${slf4j.version} - runtime - jar - - - - org.slf4j - jcl-over-slf4j - ${slf4j.version} - compile - - - - - - jasig-repository - JA-SIG Maven2 Repository - http://developer.ja-sig.org/maven2 - - - - jboss - JBoss Repository - default - http://repository.jboss.com/maven2 - - - - - - - - com.github.inspektr - inspektr-audit - ${inspektr.version} - - - - com.github.inspektr - inspektr-common - ${inspektr.version} - - - - com.github.inspektr - inspektr-support-spring - ${inspektr.version} - - - - commons-jexl - commons-jexl - 1.1 - - - commons-logging - commons-logging - - - - - - org.jasig.service - person-directory-impl - ${person.directory.version} - - - commons-logging - commons-logging - - - - - - commons-codec - commons-codec - ${commons.codec.version} - compile - - - - org.hibernate - hibernate-core - ${hibernate.core.version} - jar - - - - org.hibernate - hibernate-entitymanager - ${hibernate.core.version} - jar - - - - org.hibernate - hibernate-annotations - ${hibernate.core.version} - compile - jar - - - - org.hibernate.java-persistence - jpa-api - ${jpa.version} - compile - - - - - org.springframework.security - spring-security-cas-client - ${spring.security.version} - - - commons-logging - commons-logging - - - - - - org.springframework.security - spring-security-core - ${spring.security.version} - - - - org.springframework.security - spring-security-config - ${spring.security.version} - - - commons-logging - commons-logging - - - - - - - org.springframework - spring-aop - ${spring.version} - - - org.springframework - spring-aspects - ${spring.version} - - - - org.springframework - spring-beans - ${spring.version} - - - org.springframework - spring-context - ${spring.version} - - - org.springframework - spring-context-support - ${spring.version} - - - org.springframework - spring-core - ${spring.version} - - - commons-logging - commons-logging - - - - - org.springframework - spring-instrument - ${spring.version} - - - org.springframework - spring-jdbc - ${spring.version} - - - org.springframework - spring-jms - ${spring.version} - - - org.springframework - spring-orm - ${spring.version} - - - org.springframework - spring-oxm - ${spring.version} - - - - org.springframework - spring-test - ${spring.version} - test - - - org.springframework - spring-tx - ${spring.version} - - - org.springframework - spring-web - ${spring.version} - - - org.springframework - spring-webmvc - ${spring.version} - - - org.springframework.webflow - spring-webflow - ${spring.webflow.version} - - - commons-logging - commons-logging - - - org.springframework - spring-context - - - org.springframework - spring-web - - - - - - org.springframework.ldap - spring-ldap-core-tiger - 1.3.0.RELEASE - compile - - - commons-logging - commons-logging - - - - - - org.springframework.ldap - spring-ldap-core - 1.3.0.RELEASE - - - commons-logging - commons-logging - - - - - - javax.servlet - servlet-api - ${servlet.api.version} - - - - org.aspectj - aspectjweaver - ${aspectj.version} - - - - org.aspectj - aspectjrt - ${aspectj.version} - - - - org.hibernate - hibernate-validator - ${hibernate.validator.version} - - - - - - cas-server-core - cas-server-support-generic - cas-server-support-jdbc - cas-server-support-ldap - cas-server-support-legacy - cas-server-support-openid - cas-server-support-radius - cas-server-support-spnego - cas-server-support-trusted - cas-server-support-x509 - cas-server-integration-jboss - cas-server-integration-berkeleydb - cas-server-integration-memcached - cas-server-integration-restlet - cas-server-webapp - - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - - - - org.apache.maven.plugins - maven-jxr-plugin - - - org.apache.maven.plugins - maven-pmd-plugin - - - - - - CAS - /cas3 - 2.1.1.RELEASE - 3.0.5.RELEASE - 3.0.4.RELEASE - 2.6.3 - 1.6.7 - 1.0.0.GA - 0.9.12 - 1.1 - 4.0.2.GA - 3.5.0-CR-2 - 1.5.8 - 1.5.0-RC6 - 2.5 - 2.0-cr-1 - 1.4 - 1.2.15 - 4.7 - 2.4 - 1.0.0.GA - - diff --git a/cas-server-compatibility/pom.xml b/cas-server-compatibility/pom.xml new file mode 100644 index 000000000000..13f1c9642370 --- /dev/null +++ b/cas-server-compatibility/pom.xml @@ -0,0 +1,81 @@ + + + + + org.jasig.cas + cas-server + 4.0.0-SNAPSHOT + + 4.0.0 + cas-server-compatibility + jar + Apereo CAS Compatibility Tests + + src/test/java + + + src/test/resources + + **/*.properties + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*Tests.java + + + **/Abstract*.java + + + + + + + + + cas + casclient + 2.1.1 + + + + jwebunit + jwebunit + 1.2 + jar + + + org.springframework + spring-core + test + + + + + ${project.parent.basedir} + + diff --git a/cas-server-3.4.2/cas-server-compatibility/readme.txt b/cas-server-compatibility/readme.txt similarity index 100% rename from cas-server-3.4.2/cas-server-compatibility/readme.txt rename to cas-server-compatibility/readme.txt diff --git a/cas-server-compatibility/src/site/site.xml b/cas-server-compatibility/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-compatibility/src/site/site.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/cas-server-compatibility/src/test/java/org/jasig/cas/login/AbstractCas2ValidateCompatibilityTests.java b/cas-server-compatibility/src/test/java/org/jasig/cas/login/AbstractCas2ValidateCompatibilityTests.java new file mode 100644 index 000000000000..5b624458cf4c --- /dev/null +++ b/cas-server-compatibility/src/test/java/org/jasig/cas/login/AbstractCas2ValidateCompatibilityTests.java @@ -0,0 +1,367 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.login; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +/** + * Common test cases for /serviceValidate and /proxyValidate. + * @since 3.0.0 + */ +public abstract class AbstractCas2ValidateCompatibilityTests extends AbstractCompatibilityTests { + + /** + * The name of the compatibility test configuration property the value of which + * will be the URL of a proxy ticket receptor to which the CAS server under test + * can send proxy granting tickets. E.g., an instance of the Java CAS Client + * ProxyTicketReceptor servlet deployed behind SSL with an SSL cert trusted by the + * CAS server under test. We use this to test specifying a proxy callback URL + * and thereby obtaining a pgtiou. + */ + public static final String PROXY_RECEPTOR_URL_PROPERTY = "pgtreceptor.url"; + + public AbstractCas2ValidateCompatibilityTests() throws IOException { + super(); + } + + public AbstractCas2ValidateCompatibilityTests(final String name) throws IOException { + super(name); + } + + /** + * Returns /serviceValidate in the case of /serviceValidate, + * and /proxyValidate in the case of /proxyValidate. + * Concrete subclasses implement this method to configure the common + * tests defined here. + * @return + */ + protected abstract String getValidationPath(); + + protected final String getProxyCallbackUrl() { + return getProperties().getProperty(PROXY_RECEPTOR_URL_PROPERTY); + } + + public void verifyNoParameters() { + beginAt(getValidationPath()); + assertTextPresent("cas:authenticationFailure"); + + // TODO actually test the validation response XML. + } + + public void verifyBadServiceTicket() throws UnsupportedEncodingException { + final String service = getServiceUrl(); + beginAt(getValidationPath() + "?service=" + URLEncoder.encode(service, "UTF-8") + "&ticket=test"); + + assertTextPresent("cas:authenticationFailure"); + + // TODO do more to test that the response is actually XML, etc. etc. + } + + /** + * Test validation of a valid service ticket and that service tickets are + * not multiply validatable. + * @throws IOException + */ + public void verifyProperCredentialsAndServiceTicket() throws IOException { + final String service = getServiceUrl(); + final String encodedService = URLEncoder.encode(service, "UTF-8"); + beginAt("/login?service=" + encodedService); + setFormElement("username", getUsername()); + setFormElement("password", getGoodPassword()); + submit(); + + // read the service ticket + + final String serviceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse()); + + // great, now we have a ticket + + // let's validate it + + beginAt(getValidationPath() + "?service=" + encodedService + "&" + "ticket=" + serviceTicket); + + assertTextPresent("cas:authenticationSuccess"); + + // this assertion may be too strict. How does whitespace work here? + assertTextPresent("" + getUsername() + ""); + + // TODO do more to test that the response is actually XML, etc. etc. + + // let's validate it again and ensure that we cannot again validate + // the ticket + + beginAt(getValidationPath() + "?service=" + encodedService + "&" + "ticket=" + serviceTicket); + assertTextPresent("cas:authenticationFailure"); + + // TODO do more to test that the response is actually XML, etc. etc. + + } + + /** + * Test that renew=true, when specified both at login and ticket validation, + * validation succeeds. + * @throws IOException + */ + public void verifyRenew() throws IOException { + final String service = getServiceUrl(); + final String encodedService = URLEncoder.encode(service, "UTF-8"); + beginAt("/login?renew=true&service=" + encodedService); + setFormElement("username", getUsername()); + setFormElement("password", getGoodPassword()); + submit(); + + // read the service ticket + + final String serviceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse()); + + // great, now we have a ticket + + // let's validate it + + beginAt(getValidationPath() + "?renew=true&service=" + encodedService + "&" + "ticket=" + serviceTicket); + + assertTextPresent("cas:authenticationSuccess"); + } + + /** + * Test a couple renew=true logins... + * @throws IOException + */ + public void verifyMultipleRenew() throws IOException { + final String service = getServiceUrl(); + final String encodedService = URLEncoder.encode(service, "UTF-8"); + + beginAt("/login?renew=true&service=" + encodedService); + setFormElement("username", getUsername()); + setFormElement("password", getGoodPassword()); + submit(); + + // read the service ticket + + String serviceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse()); + + // great, now we have a ticket + + // let's validate it + + beginAt(getValidationPath() + "?service=" + encodedService + "&" + "ticket=" + serviceTicket + "&renew=true"); + + assertTextPresent("cas:authenticationSuccess"); + + // now let's do it again + + beginAt("/login?renew=true&service=" + encodedService); + setFormElement("username", getUsername()); + setFormElement("password", getGoodPassword()); + submit(); + + // read the service ticket + + serviceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse()); + + // great, now we have a ticket + + // let's validate it + + beginAt(getValidationPath() + "?service=" + encodedService + "&" + "ticket=" + serviceTicket + "&renew=true"); + + assertTextPresent(""); + + } + + /** + * Test that renew=true, when specified only at ticket validation, + * validation succeeds if username, password were presented at login even + * though renew wasn't set then. + * @throws IOException + */ + public void verifyAccidentalRenew() throws IOException { + final String service = getServiceUrl(); + final String encodedService = URLEncoder.encode(service, "UTF-8"); + beginAt("/login?service=" + encodedService); + setFormElement("username", getUsername()); + setFormElement("password", getGoodPassword()); + submit(); + + // read the service ticket + + final String serviceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse()); + + // great, now we have a ticket + + // let's validate it + + beginAt(getValidationPath() + "?renew=true&service=" + encodedService + "&" + "ticket=" + serviceTicket); + + assertTextPresent(""); + assertTextPresent("" + getUsername() + ""); + } + + /** + * Test that renew at ticket validation blocks validation of a ticket + * vended via SSO. + * @throws IOException + */ + public void verifyRenewBlocksSsoValidation() throws IOException { + + // initial authentication + final String firstService = getServiceUrl(); + final String encodedFirstService = URLEncoder.encode(firstService, "UTF-8"); + beginAt("/login?service=" + encodedFirstService); + setFormElement("username", getUsername()); + setFormElement("password", getGoodPassword()); + submit(); + + final String firstServiceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse()); + + // that established SSO. Now let's get another ticket via SSO + + final String secondService= "http://www.uportal.org/"; + final String encodedSecondService = URLEncoder.encode(secondService, "UTF-8"); + + beginAt("/login?service=" + encodedSecondService); + + // read the service ticket + + final String secondServiceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse()); + + // let's validate the second (non-renew) ticket. + + beginAt(getValidationPath() + "?renew=true&service=" + encodedSecondService + "&ticket=" + secondServiceTicket); + + assertTextPresent("cas:authenticationFailure"); + + //TODO test the authentication failure response in more detail + + assertTextNotPresent(""); + + // however, we can validate the first ticket with renew=true. + + beginAt(getValidationPath() + "?renew=true&service=" + encodedFirstService + "&ticket=" + firstServiceTicket); + + assertTextPresent("cas:authenticationSuccess"); + assertTextPresent("" + getUsername() + ""); + //TODO assert more about the response + + } + + /** + * Test best-effort ticket validation when a specified proxy callback handler + * doesn't really exist. + * @throws IOException + */ + public void verifyBrokenProxyCallbackUrl() throws IOException { + + final String service = getServiceUrl(); + final String encodedService = URLEncoder.encode(service, "UTF-8"); + beginAt("/login?service=" + encodedService); + setFormElement("username", getUsername()); + setFormElement("password", getGoodPassword()); + submit(); + + // read the service ticket + + final String serviceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse()); + + // great, now we have a ticket + + // let's validate it, specifying a bogus pgt callback + + final String encodedProxyCallbackUrl = URLEncoder.encode("https://secure.its.yale.edu/cas/noexist", "UTF-8"); + + beginAt(getValidationPath() + "?renew=true&service=" + encodedService + "&" + "ticket=" + + serviceTicket + "&pgtUrl=" + encodedProxyCallbackUrl); + + assertTextPresent(""); + assertTextPresent("" + getUsername() + ""); + + // no pgtiou because failure in sending pgt to specified receptor URL. + assertTextNotPresent(""); + + } + + public void verifyPgtAcquisition() throws IOException { + + final String service = getServiceUrl(); + final String encodedService = URLEncoder.encode(service, "UTF-8"); + beginAt("/login?service=" + encodedService); + setFormElement("username", getUsername()); + setFormElement("password", getGoodPassword()); + submit(); + + // read the service ticket + + final String serviceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse()); + + // great, now we have a ticket + + // let's validate it, specifying a bogus pgt callback + + final String encodedProxyCallbackUrl = URLEncoder.encode(getProxyCallbackUrl(), "UTF-8"); + + beginAt(getValidationPath() + "?renew=true&service=" + encodedService + "&" + "ticket=" + serviceTicket + + "&pgtUrl=" + encodedProxyCallbackUrl); + + assertTextPresent(""); + assertTextPresent("" + getUsername() + ""); + // pgtiou because success in sending pgt + assertTextPresent(""); + + } + + /** + * Test for JIRA issue CAS-224. + * 1. /cas/login?service=foo&renew=true + * 2. log in + * 3. /serviceValidate?ticket=[ticket]&service=foo&renew=true + * + * Issue was that validation fails whereas it should succeed. + * + * This testcase is almost certainly redundant, but it's explicitly here + * to cover this issue. + * @throws IOException + */ + public void verify224() throws IOException { + + final String service = getServiceUrl(); + final String encodedService = URLEncoder.encode(service, "UTF-8"); + + beginAt("/login?service=" + encodedService + "&renew=true"); + setFormElement("username", getUsername()); + setFormElement("password", getGoodPassword()); + submit(); + + // read the service ticket + + final String serviceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse()); + + // great, now we have a ticket + + // let's validate it + assertNotNull(serviceTicket); + + beginAt(getValidationPath() + "?ticket=" + serviceTicket + "&service=" + encodedService + "&renew=true"); + + assertTextPresent("cas:authenticationSuccess"); + + } + +} diff --git a/cas-server-compatibility/src/test/java/org/jasig/cas/login/AbstractCompatibilityTests.java b/cas-server-compatibility/src/test/java/org/jasig/cas/login/AbstractCompatibilityTests.java new file mode 100644 index 000000000000..b414a42d066d --- /dev/null +++ b/cas-server-compatibility/src/test/java/org/jasig/cas/login/AbstractCompatibilityTests.java @@ -0,0 +1,134 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.login; + +import java.io.IOException; +import java.util.Properties; + +import org.springframework.core.io.ClassPathResource; + +import net.sourceforge.jwebunit.WebTestCase; + +/** + * Base class for all Web compatibility tests. + */ +public abstract class AbstractCompatibilityTests extends WebTestCase { + + public static final String LOGIN_TOKEN = "lt"; + + public static final String COOKIE_TGC_ID = "CASTGC"; + + /** + * The name of our properties configuration while, which we expect on the + * classpath. + */ + public static final String PROPERTIES_FILE_NAME = "configuration.properties"; + + private final Properties properties = new Properties(); + + /** + * The name of the compatibility test configuration property the value of which + * will be the base URL of the CAS server, e.g. for Yale's production CAS server + * server.url=https://secure.its.yale.edu/cas + */ + public static final String SERVER_URL_PROPERTY = "server.url"; + + /** + * The name of the compatibility test configuration property the value of + * which will be the username as whom we should try to authenticate. + */ + public static final String USERNAME_PROPERTY = "credentials.username"; + + /** + * The name of the compatibility test configuration property the value of + * which will be a correct password for the username. + */ + public static final String GOOD_PASSWORD_PROPERTY = "credentials.goodPassword"; + + /** + * The name of the compatibility test configuration property the value of + * which will be an incorrect password for the username. + */ + public static final String BAD_PASSWORD_PROPERTY = "credentials.badPassword"; + + + /** + * The name of the compatibility test configuration property the value of + * which will be a URL to a service we can use for testing. + */ + public static final String SERVICE_URL_PROPERTY = "service.url"; + + protected AbstractCompatibilityTests() throws IOException { + super(); + setUpTest(); + } + + protected AbstractCompatibilityTests(final String name) throws IOException { + super(name); + setUpTest(); + } + + private void setUpTest() throws IOException { + this.properties.load(new ClassPathResource(PROPERTIES_FILE_NAME).getInputStream()); + getTestContext().setBaseUrl(this.properties.getProperty(SERVER_URL_PROPERTY)); + } + + /** + * Get the Properties parsed at instantiation from the compatibility + * tests configuration file. + * @return Properties from our configuration file. + */ + protected final Properties getProperties() { + return this.properties; + } + + /** + * Get the username as which we should test authenticating. + * @return the username + */ + protected final String getUsername(){ + return getProperties().getProperty(USERNAME_PROPERTY); + } + + /** + * Get the correct password for authenticating as the username given by + * getUsername(). + * @return the correct password + */ + protected final String getGoodPassword() { + return getProperties().getProperty(GOOD_PASSWORD_PROPERTY); + } + + /** + * Get an incorrect password for the username given by getUsername(). + * @return an incorrect password. + */ + protected final String getBadPassword() { + return getProperties().getProperty(BAD_PASSWORD_PROPERTY); + } + + /** + * Get the configured URL we are to use as an example service for which + * we acquire service tickets. + * @return example service URL + */ + protected final String getServiceUrl() { + return getProperties().getProperty(SERVICE_URL_PROPERTY); + } +} diff --git a/cas-server-compatibility/src/test/java/org/jasig/cas/login/AbstractLoginCompatibilityTests.java b/cas-server-compatibility/src/test/java/org/jasig/cas/login/AbstractLoginCompatibilityTests.java new file mode 100644 index 000000000000..2e7f1216311b --- /dev/null +++ b/cas-server-compatibility/src/test/java/org/jasig/cas/login/AbstractLoginCompatibilityTests.java @@ -0,0 +1,60 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.login; + +import java.io.IOException; + +/** + * Base class for Web compatibility tests around CAS login form. + */ +public abstract class AbstractLoginCompatibilityTests extends AbstractCompatibilityTests { + public static final String FORM_USERNAME = "username"; + public static final String FORM_PASSWORD = "password"; + + /** + * The name of the compatibility test configuration property the value of which + * should be an alternate username to test logging into the CAS server. + */ + public static final String ALTERNATE_USERNAME_PROPERTY = "credentials.alternate.username"; + + /** + * The name of the compatibility test configuration property the value of which should be + * the correct password to go with that alternate username. + */ + public static final String ALTERNATE_PASSWORD_PROPERTY = "credentials.alternate.password"; + + public AbstractLoginCompatibilityTests() throws IOException { + super(); + } + + public AbstractLoginCompatibilityTests(final String name) throws IOException { + super(name); + } + + protected String getAlternateUsername(){ + return getProperties().getProperty(ALTERNATE_USERNAME_PROPERTY); + } + + protected String getAlternatePassword() { + return getProperties().getProperty(ALTERNATE_PASSWORD_PROPERTY); + } + + + +} diff --git a/cas-server-compatibility/src/test/java/org/jasig/cas/login/AllTests.java b/cas-server-compatibility/src/test/java/org/jasig/cas/login/AllTests.java new file mode 100644 index 000000000000..ecc7ee5e489d --- /dev/null +++ b/cas-server-compatibility/src/test/java/org/jasig/cas/login/AllTests.java @@ -0,0 +1,33 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.login; + +import org.junit.runners.Suite; +import org.junit.runner.RunWith; + +/** + * Suite of compatibility tests. + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ValidateCompatibilityTests.class, LoginAsCredentialsAcceptorCompatibilityTests.class, + ProxyValidateCompatibilityTests.class, LogoutCompatibilityTests.class, + LoginAsCredentialsRequestorCompatibilityTests.class, ServiceValidateCompatibilityTests.class}) +public class AllTests { +} diff --git a/cas-server-compatibility/src/test/java/org/jasig/cas/login/LoginAsCredentialsAcceptorCompatibilityTests.java b/cas-server-compatibility/src/test/java/org/jasig/cas/login/LoginAsCredentialsAcceptorCompatibilityTests.java new file mode 100644 index 000000000000..d92386789069 --- /dev/null +++ b/cas-server-compatibility/src/test/java/org/jasig/cas/login/LoginAsCredentialsAcceptorCompatibilityTests.java @@ -0,0 +1,169 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.login; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +/** + * + * @author Scott Battaglia + * @author Drew Mazurek + * @since 3.0.0 + * + */ +public class LoginAsCredentialsAcceptorCompatibilityTests extends AbstractLoginCompatibilityTests { + + public LoginAsCredentialsAcceptorCompatibilityTests() throws IOException { + super(); + } + + public LoginAsCredentialsAcceptorCompatibilityTests(final String name) throws IOException { + super(name); + } + + public void verifySingleSignOn() { + beginAt("/login"); + setFormElement(FORM_USERNAME, getUsername()); + setFormElement(FORM_PASSWORD, getGoodPassword()); + submit(); + assertCookiePresent(COOKIE_TGC_ID); + assertFormNotPresent(); + + // TODO test logging in to another service + } + + public void verifyValidCredentialsAuthenticationWithWarn() throws IOException { + final String service = "http://www.yale.edu"; + beginAt("/login?service=" + URLEncoder.encode(service, "UTF-8")); + setFormElement(FORM_USERNAME, getUsername()); + setFormElement(FORM_PASSWORD, getGoodPassword()); + getDialog().getForm().setCheckbox("warn", true); + submit(); + + final String anotherService = "https://secure.its.yale.edu/cas"; + final String anotherServiceEncoded = URLEncoder.encode(anotherService, "UTF-8"); + + beginAt("/login?service=" + anotherServiceEncoded); + + // since warn was set, CAS should not redirect us immediately to the service, + // but should rather interpose a warning screen. + + assertTextPresent(anotherService); + + + } + + public void verifyValidCredentialsAuthenticationWithoutWarn() throws UnsupportedEncodingException { + final String service = "http://www.cnn.com"; + beginAt("/login?service=" + URLEncoder.encode(service, "UTF-8")); + setFormElement(FORM_USERNAME, getUsername()); + setFormElement(FORM_PASSWORD, getGoodPassword()); + submit(); + // TODO testValidCredentialsAuthenticationWithoutWarn + } + + /* + * jWebUnit doesn't allow you to change pre-populated hidden form values. + * + public void verifyBadLoginTicket() { + setFormElement(FORM_USERNAME, "test"); + setFormElement(FORM_PASSWORD, "test"); + setFormElement(WebConstants.LOGIN_TOKEN, "test"); + + submit(); + assertFormElementPresent(FORM_USERNAME); + } + + public void verifyNoLoginTicket() { + setFormElement(FORM_USERNAME, "test"); + setFormElement(FORM_PASSWORD, "test"); + setFormElement(WebConstants.LOGIN_TOKEN, ""); + submit(); + assertFormElementPresent(FORM_USERNAME); + } + + public void verifyDoubleLoginTicket() { + //TODO covered by badLoginTicket? + } + * + */ + + public void verifyPassBadCredentials() { + beginAt("/login"); + setFormElement(FORM_USERNAME, getUsername()); + setFormElement(FORM_PASSWORD, getBadPassword()); + submit(); + assertFormElementPresent(FORM_USERNAME); + } + + public void verifyPassEmptyCredentials() { + beginAt("/login"); + submit(); + assertFormElementPresent(FORM_USERNAME); + } + + /** + * Test that logging in as someone else destroys the TGT and outstanding + * service tickets for the previously authenticated user. + * @throws IOException + */ + public void verifyLoginAsSomeoneElse() throws IOException { + final String encodedService = URLEncoder.encode(getServiceUrl(), "UTF-8"); + + // establish SSO session as the first user + + beginAt("/login?service=" + encodedService); + setFormElement(FORM_USERNAME, getUsername()); + setFormElement(FORM_PASSWORD, getGoodPassword()); + submit(); + + // get the service ticket + + final String firstServiceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse()); + + // now login via renew as someone else + + + beginAt("/login?renew=true&service=" + encodedService); + setFormElement(FORM_USERNAME, getAlternateUsername()); + setFormElement(FORM_PASSWORD, getAlternatePassword()); + submit(); + + // get the service ticket + + final String secondServiceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse()); + + // validate the second service ticket + + beginAt("/serviceValidate?ticket=" + secondServiceTicket + "&service=" + encodedService); + + assertTextPresent("" + getAlternateUsername() + ""); + + + // okay, now attempt to validate the original service ticket + // and see that it has been invalidated + + beginAt("/serviceValidate?ticket=" + firstServiceTicket + "&service=" + encodedService); + assertTextPresent(" -1 && (endDoubleQuoteIndex < endSingleQuoteIndex || endSingleQuoteIndex == -1)) { + endQuoteIndex = endDoubleQuoteIndex; + } else { + endQuoteIndex = endSingleQuoteIndex; + } + + final int ticketEqualsIndex = responseAfterWindowLocHref.indexOf("ticket="); + + serviceTicket = responseAfterWindowLocHref.substring(ticketEqualsIndex + "ticket=".length(), endQuoteIndex); + + + } else { + // service ticket was found on query String, parse it from there + + // TODO Is this type of redirection compatible? + // Does it address all the issues that CAS2 JavaScript redirection + // was intended to address? + + serviceTicket = queryString.substring(ticketIndex + "ticket=".length(), queryString.length()); + + } + + return serviceTicket; + } + +} diff --git a/cas-server-compatibility/src/test/java/org/jasig/cas/login/LogoutCompatibilityTests.java b/cas-server-compatibility/src/test/java/org/jasig/cas/login/LogoutCompatibilityTests.java new file mode 100644 index 000000000000..22a0d936ea90 --- /dev/null +++ b/cas-server-compatibility/src/test/java/org/jasig/cas/login/LogoutCompatibilityTests.java @@ -0,0 +1,116 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.login; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +/** + * + * @author Scott Battaglia + * @since 3.0.0 + * + */ +public class LogoutCompatibilityTests extends AbstractCompatibilityTests { + + public LogoutCompatibilityTests() throws IOException { + super(); + } + + public LogoutCompatibilityTests(final String name) throws IOException { + super(name); + } + + /** + * Test that the logout UI follows the recommended behavior of painting + * a link to the URL specified by an application redirecting for logout. + * + * CAS servers failing this test are not necessarily CAS2 non-compliant, as + * support for this behavior is recommended but not required. + * @throws UnsupportedEncodingException + */ + public void verifyUrlParameter() throws UnsupportedEncodingException { + final String service = "https://localhost:8443/contacts-cas/j_acegi_cas_security_check"; + beginAt("/logout?url=" + URLEncoder.encode(service, "UTF-8")); + + assertTextPresent(service); + } + + public void verifyShowLoggedOutPage() { + beginAt("/logout"); + + assertTextPresent("logged out"); + } + + + /** + * Test that after logout SSO doesn't happen - visiting login + * leads to the login screen. Also test that logout renders a previous + * service ticket invalid. + * @throws IOException + */ + public void verifyLogoutEndsSso() throws IOException { + // demonstrate lack of SSO session + final String serviceUrl = getServiceUrl(); + final String encodedService = URLEncoder.encode(serviceUrl, "UTF-8"); + beginAt("/login?service=" + encodedService); + + // verify that login screen is painted + assertFormElementPresent(LOGIN_TOKEN); + + // establish SSO session + + setFormElement("username", getUsername()); + setFormElement("password", getGoodPassword()); + submit(); + + final String firstServiceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse()); + assertNotNull(firstServiceTicket); + + // Demonstrate successful validation of st before logout + + beginAt("/serviceValidate?service=" + encodedService + "&ticket=" + firstServiceTicket); + assertTextPresent(" + + + + + cas-server + org.jasig.cas + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-core-api + Apereo CAS Core APIs + CAS core public APIs + + + + org.apache.commons + commons-collections4 + + + + org.apache.commons + commons-lang3 + + + + + ${project.parent.basedir} + + diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/CasProtocolConstants.java b/cas-server-core-api/src/main/java/org/jasig/cas/CasProtocolConstants.java new file mode 100644 index 000000000000..013eab523e5f --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/CasProtocolConstants.java @@ -0,0 +1,70 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas; + +/** + * Class that exposes relevant constants and parameters to + * the CAS protocol. These include attribute names, pre-defined + * values and expected request parameter names as is specified + * by the protocol. + * + * @author Misagh Moayyed + * @since 4.1.0 + */ +public interface CasProtocolConstants { + + /** CAS Protocol Validation Fields. **/ + + /** Constant representing the PGT in the cas validation model. */ + String VALIDATION_CAS_MODEL_PROXY_GRANTING_TICKET = "proxyGrantingTicket"; + + /** Constant representing the PGTIOU in the cas validation model. */ + String VALIDATION_CAS_MODEL_PROXY_GRANTING_TICKET_IOU = "pgtIou"; + + /** Constant representing the remember-me long term token in the validation payload. */ + String VALIDATION_REMEMBER_ME_ATTRIBUTE_NAME = "longTermAuthenticationRequestTokenUsed"; + + /** Represents the collection of attributes in the view. */ + String VALIDATION_CAS_MODEL_ATTRIBUTE_NAME_ATTRIBUTES = "attributes"; + + /** Represents the authentication date object in the view. */ + String VALIDATION_CAS_MODEL_ATTRIBUTE_NAME_AUTHENTICATION_DATE = "authenticationDate"; + + /** Represents the flag to note whether assertion is backed by new login. */ + String VALIDATION_CAS_MODEL_ATTRIBUTE_NAME_FROM_NEW_LOGIN = "isFromNewLogin"; + + /** CAS Protocol Parameters. **/ + + /** Constant representing the proxy callback url parameter in the request. */ + String PARAMETER_PROXY_CALLBACK_URL = "pgtUrl"; + + /** Constant representing the renew parameter in the request. */ + String PARAMETER_RENEW = "renew"; + + /** CAS Protocol Error Codes. **/ + + /** Constant representing an invalid request for validation. */ + String ERROR_CODE_INVALID_REQUEST = "INVALID_REQUEST"; + + /** Constant representing an invalid proxy callback for validation. */ + String ERROR_CODE_INVALID_PROXY_CALLBACK = "INVALID_PROXY_CALLBACK"; + + /** Constant representing an invalid ticket for validation. */ + String ERROR_CODE_INVALID_TICKET = "INVALID_TICKET"; +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/CentralAuthenticationService.java b/cas-server-core-api/src/main/java/org/jasig/cas/CentralAuthenticationService.java new file mode 100644 index 000000000000..93169fe2e326 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/CentralAuthenticationService.java @@ -0,0 +1,180 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas; + +import org.apache.commons.collections4.Predicate; +import org.jasig.cas.authentication.AuthenticationException; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.logout.LogoutRequest; +import org.jasig.cas.ticket.TicketException; +import org.jasig.cas.ticket.InvalidTicketException; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.validation.Assertion; + +import javax.validation.constraints.NotNull; +import java.util.Collection; +import java.util.List; + +/** + * CAS viewed as a set of services to generate and validate Tickets. + *

+ * This is the interface between a Web HTML, Web Services, RMI, or any other + * request processing layer and the CAS Service viewed as a mechanism to + * generate, store, validate, and retrieve Tickets containing Authentication + * information. The features of the request processing layer (the HttpXXX + * Servlet objects) are not visible here or in any modules behind this layer. In + * theory, a standalone application could call these methods directly as a + * private authentication service. + *

+ * + * @author William G. Thompson, Jr. + * @author Dmitry Kopylenko + * @author Scott Battaglia + * @author Marvin S. Addison + * @author Misagh Moayyed + * @since 3.0.0 + */ +public interface CentralAuthenticationService { + + /** + * Create a {@link org.jasig.cas.ticket.TicketGrantingTicket} by authenticating credentials. + * The details of the security policy around credential authentication and the definition + * of authentication success are dependent on the implementation, but it SHOULD be safe to assume + * that at least one credential MUST be authenticated for ticket creation to succeed. + * + * @param credentials One or more credentials that may be authenticated in order to create the ticket. + * + * @return Non-null ticket-granting ticket identifier. + * + * @throws AuthenticationException on errors authenticating the credentials + * @throws org.jasig.cas.ticket.TicketException if ticket cannot be created + */ + TicketGrantingTicket createTicketGrantingTicket(@NotNull Credential... credentials) + throws AuthenticationException, TicketException; + + + /** + * Obtains the given ticket by its id and type + * and returns the CAS-representative object. Implementations + * need to check for the validity of the ticket by making sure + * it exists and has not expired yet, etc. This method is specifically + * designed to remove the need to access the ticket registry. + * + * @param ticketId the ticket granting ticket id + * @param clazz the ticket type that is reques to be found + * @param the generic ticket type to return that extends {@link Ticket} + * @return the ticket object + * @throws org.jasig.cas.ticket.InvalidTicketException if ticket is not found or has expired. + * @since 4.1.0 + */ + T getTicket(@NotNull String ticketId, @NotNull Class clazz) + throws InvalidTicketException; + + /** + * Retrieve a collection of tickets from the underlying ticket registry. + * The retrieval operation must pass the predicate check that is solely + * used to filter the collection of tickets received. Implementations + * can use the predicate to request a collection of expired tickets, + * or tickets whose id matches a certain pattern, etc. The resulting + * collection will include ticktes that have been evaluated by the predicate. + * + * @param predicate the predicate + * @return the tickets + * @since 4.1.0 + */ + Collection getTickets(@NotNull Predicate predicate); + + /** + * Grants a {@link org.jasig.cas.ticket.ServiceTicket} that may be used to access the given service. + * + * @param ticketGrantingTicketId Proof of prior authentication. + * @param service The target service of the ServiceTicket. + * + * @return Non-null service ticket identifier. + * + * @throws org.jasig.cas.ticket.TicketException if the ticket could not be created. + */ + ServiceTicket grantServiceTicket(@NotNull String ticketGrantingTicketId, @NotNull Service service) throws TicketException; + + /** + * Grant a {@link org.jasig.cas.ticket.ServiceTicket} that may be used to access the given service + * by authenticating the given credentials. + * The details of the security policy around credential authentication and the definition + * of authentication success are dependent on the implementation, but it SHOULD be safe to assume + * that at least one credential MUST be authenticated for ticket creation to succeed. + *

+ * The principal that is resolved from the authenticated credentials MUST be the same as that to which + * the given ticket-granting ticket was issued. + *

+ * + * @param ticketGrantingTicketId Proof of prior authentication. + * @param service The target service of the ServiceTicket. + * @param credentials One or more credentials to authenticate prior to granting the service ticket. + * + * @return Non-null service ticket identifier. + * + * @throws AuthenticationException on errors authenticating the credentials + * @throws org.jasig.cas.ticket.TicketException if the ticket could not be created. + */ + ServiceTicket grantServiceTicket( + @NotNull final String ticketGrantingTicketId, @NotNull final Service service, final Credential... credentials) + throws AuthenticationException, TicketException; + + /** + * Validate a ServiceTicket for a particular Service. + * + * @param serviceTicketId Proof of prior authentication. + * @param service Service wishing to validate a prior authentication. + * + * @return Non-null ticket validation assertion. + * + * @throws org.jasig.cas.ticket.TicketException if there was an error validating the ticket. + */ + Assertion validateServiceTicket(@NotNull final String serviceTicketId, @NotNull final Service service) throws TicketException; + + /** + * Destroy a TicketGrantingTicket and perform back channel logout. This has the effect of invalidating any + * Ticket that was derived from the TicketGrantingTicket being destroyed. May throw an + * {@link IllegalArgumentException} if the TicketGrantingTicket ID is null. + * + * @param ticketGrantingTicketId the id of the ticket we want to destroy + * @return the logout requests. + */ + List destroyTicketGrantingTicket(@NotNull final String ticketGrantingTicketId); + + /** + * Delegate a TicketGrantingTicket to a Service for proxying authentication + * to other Services. + * + * @param serviceTicketId The service ticket identifier that will delegate to a + * {@link org.jasig.cas.ticket.TicketGrantingTicket}. + * @param credentials One or more credentials to authenticate prior to delegating the ticket. + * + * @return Non-null ticket-granting ticket identifier that can grant {@link org.jasig.cas.ticket.ServiceTicket} + * that proxy authentication. + * + * @throws AuthenticationException on errors authenticating the credentials + * @throws org.jasig.cas.ticket.TicketException if there was an error creating the ticket + */ + TicketGrantingTicket delegateTicketGrantingTicket(@NotNull final String serviceTicketId, @NotNull final Credential... credentials) + throws AuthenticationException, TicketException; +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/MessageDescriptor.java b/cas-server-core-api/src/main/java/org/jasig/cas/MessageDescriptor.java new file mode 100644 index 000000000000..7f8cc4c46a60 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/MessageDescriptor.java @@ -0,0 +1,53 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas; + +import java.io.Serializable; + +/** + * Simple parameterized message descriptor with a code that refers to a message bundle key and a default + * message string to use if no message code can be resolved. + * + * @author Misagh Moayyed + * @since 4.1.0 + */ +public interface MessageDescriptor extends Serializable { + + /** + * Gets code. + * + * @return the code + */ + String getCode(); + + /** + * Gets default message. + * + * @return the default message + */ + String getDefaultMessage(); + + /** + * Get params. + * + * @return the serializable [ ] + */ + Serializable[] getParams(); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/Authentication.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/Authentication.java new file mode 100644 index 000000000000..77181829c340 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/Authentication.java @@ -0,0 +1,97 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.jasig.cas.authentication.principal.Principal; + +/** + *

+ * The Authentication object represents a successful authentication request. It + * contains the principal that the authentication request was made for as well + * as the additional meta information such as the authenticated date and a map + * of attributes. + *

+ *

+ * An Authentication object must be serializable to permit persistance and + * clustering. + *

+ *

+ * Implementing classes must take care to ensure that the Map returned by + * getAttributes is serializable by using a Serializable map such as HashMap. + *

+ * + * @author Dmitriy Kopylenko + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.0.0 + */ +public interface Authentication extends Serializable { + + /** + * Method to obtain the Principal. + * + * @return a Principal implementation + */ + Principal getPrincipal(); + + /** + * Method to retrieve the timestamp of when this Authentication object was + * created. + * + * @return the date/time the authentication occurred. + */ + Date getAuthenticationDate(); + + /** + * Attributes of the authentication (not the Principal). + * + * @return the map of attributes. + */ + Map getAttributes(); + + /** + * Gets a list of metadata about the credentials supplied at authentication time. + * + * @return Non-null list of supplied credentials represented as metadata that should be considered safe for + * long-term storage (e.g. serializable and secure with respect to credential disclosure). The order of items in + * the returned list SHOULD be the same as the order in which the source credentials were presented and subsequently + * processed. + */ + List getCredentials(); + + /** + * Gets a map describing successful authentications produced by {@link AuthenticationHandler} components. + * + * @return Map of handler names to successful authentication result produced by that handler. + */ + Map getSuccesses(); + + /** + * Gets a map describing failed authentications. By definition the failures here were not sufficient to prevent + * authentication. + * + * @return Map of authentication handler names to the authentication errors produced on attempted authentication. + */ + Map> getFailures(); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationBuilder.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationBuilder.java new file mode 100644 index 000000000000..9378a17b6937 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationBuilder.java @@ -0,0 +1,130 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.authentication; + +import org.jasig.cas.authentication.principal.Principal; + +import java.io.Serializable; +import java.util.Date; +import java.util.Map; + +/** + * Constructs immutable {@link Authentication} objects using the builder pattern. + * + * @author Misagh Moayyed + * @since 4.1.0 + */ +public interface AuthenticationBuilder extends Serializable { + + /** + * Gets the authenticated principal. + * + * @return Principal. + */ + Principal getPrincipal(); + + /** + * Sets the principal returns this instance. + * + * @param p Authenticated principal. + * + * @return This builder instance. + */ + AuthenticationBuilder setPrincipal(Principal p); + + /** + * Adds metadata about a credential presented for authentication. + * + * @param credential Credential metadata. + * + * @return This builder instance. + */ + AuthenticationBuilder addCredential(CredentialMetaData credential); + + /** + * Adds an authentication metadata attribute key-value pair. + * + * @param key Authentication attribute key. + * @param value Authentication attribute value. + * + * @return This builder instance. + */ + AuthenticationBuilder addAttribute(String key, Object value); + + /** + * Gets the authentication success map. + * + * @return Non-null map of handler names to successful handler authentication results. + */ + Map getSuccesses(); + + /** + * Adds an authentication success to the map of handler names to successful authentication handler results. + * + * @param key Authentication handler name. + * @param value Successful authentication handler result produced by handler of given name. + * + * @return This builder instance. + */ + AuthenticationBuilder addSuccess(String key, HandlerResult value); + + /** + * Sets the authentication date and returns this instance. + * + * @param d Authentication date. + * + * @return This builder instance. + */ + AuthenticationBuilder setAuthenticationDate(Date d); + + /** + * Creates an immutable authentication instance from builder data. + * + * @return Immutable authentication. + */ + Authentication build(); + + /** + * Gets the authentication failure map. + * + * @return Non-null authentication failure map. + */ + Map> getFailures(); + + /** + * Adds an authentication failure to the map of handler names to the authentication handler failures. + * + * @param key Authentication handler name. + * @param value Exception raised on handler failure to authenticate credential. + * + * @return This builder instance. + */ + AuthenticationBuilder addFailure(String key, Class value); + + /** + * Sets the authentication metadata attributes. + * + * @param attributes Non-null map of authentication metadata attributes. + * + * @return This builder instance. + */ + AuthenticationBuilder setAttributes(Map attributes); + +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationException.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationException.java new file mode 100644 index 000000000000..cb159fbfb28f --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationException.java @@ -0,0 +1,111 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import java.util.Collections; +import java.util.Map; + +/** + * Authentication raised by {@link AuthenticationManager} to signal authentication failure. + * Authentication failure typically occurs when one or more {@link AuthenticationHandler} components + * fail to authenticate credentials. This exception contains information about handler successes + * and failures that may be used by higher-level components to determine subsequent behavior. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class AuthenticationException extends Exception { + + /** Serialization metadata. */ + private static final long serialVersionUID = -6032827784134751797L; + + /** Immutable map of handler names to the errors they raised. */ + private final Map> handlerErrors; + + /** Immutable map of handler names to an authentication success metadata instance. */ + private final Map handlerSuccesses; + + /** + * Creates a new instance for the case when no handlers were attempted, i.e. no successes or failures. + */ + public AuthenticationException() { + this( + "No supported authentication handlers found for given credentials.", + Collections.>emptyMap(), + Collections.emptyMap()); + } + + /** + * Creates a new instance for the case when no handlers succeeded. + * + * @param handlerErrors Map of handler names to errors. + */ + public AuthenticationException(final Map> handlerErrors) { + this(handlerErrors, Collections.emptyMap()); + } + + /** + * Creates a new instance for the case when there are both handler successes and failures. + * + * @param handlerErrors Map of handler names to errors. + * @param handlerSuccesses Map of handler names to authentication successes. + */ + public AuthenticationException( + final Map> handlerErrors, final Map handlerSuccesses) { + this( + String.format("%s errors, %s successes", handlerErrors.size(), handlerSuccesses.size()), + handlerErrors, + handlerSuccesses); + } + + /** + * Creates a new instance for the case when there are both handler successes and failures and a custom + * error message is required. + * + * @param message the message associated with this error. + * @param handlerErrors Map of handler names to errors. + * @param handlerSuccesses Map of handler names to authentication successes. + */ + public AuthenticationException( + final String message, + final Map> handlerErrors, + final Map handlerSuccesses) { + super(message); + this.handlerErrors = Collections.unmodifiableMap(handlerErrors); + this.handlerSuccesses = Collections.unmodifiableMap(handlerSuccesses); + } + + /** + * Gets an unmodifable map of handler names to errors. + * + * @return Immutable map of handler names to errors. + */ + public Map> getHandlerErrors() { + return this.handlerErrors; + } + + /** + * Gets an unmodifable map of handler names to authentication successes. + * + * @return Immutable map of handler names to authentication successes. + */ + public Map getHandlerSuccesses() { + return this.handlerSuccesses; + } +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationHandler.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationHandler.java new file mode 100644 index 000000000000..d4ca7f63c8d7 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationHandler.java @@ -0,0 +1,91 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import java.security.GeneralSecurityException; + +/** + * An authentication handler authenticates a single credential. In many cases credentials are authenticated by + * comparison with data in a system of record such as LDAP directory or database. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public interface AuthenticationHandler { + + /** + * Authenticates the given credential. There are three possible outcomes of this process, and implementers + * MUST adhere to the following contract: + * + *
    + *
  1. Success -- return {@link HandlerResult}
  2. + *
  3. Failure -- throw {@link GeneralSecurityException}
  4. + *
  5. Indeterminate -- throw {@link PreventedException}
  6. + *
+ * + * @param credential The credential to authenticate. + * + * @return A result object containing metadata about a successful authentication event that includes at + * a minimum the name of the handler that authenticated the credential and some credential metadata. + * The following data is optional: + *
    + *
  • {@link org.jasig.cas.authentication.principal.Principal}
  • + *
  • Messages issued by the handler about the credential (e.g. impending password expiration warning)
  • + *
+ * + * @throws GeneralSecurityException On authentication failures where the root cause is security related, + * e.g. invalid credential. Implementing classes SHOULD be as specific as possible in communicating the reason for + * authentication failure. Recommendations for common cases: + *
    + *
  • Bad password: {@link javax.security.auth.login.FailedLoginException}
  • + *
  • Expired password: {@link javax.security.auth.login.CredentialExpiredException}
  • + *
  • User account expired: {@link javax.security.auth.login.AccountExpiredException}
  • + *
  • User account locked: {@link javax.security.auth.login.AccountLockedException}
  • + *
  • User account not found: {@link javax.security.auth.login.AccountNotFoundException}
  • + *
  • Time of authentication not allowed: {@link InvalidLoginTimeException}
  • + *
  • Location of authentication not allowed: {@link InvalidLoginLocationException}
  • + *
  • Expired X.509 certificate: {@link java.security.cert.CertificateExpiredException}
  • + *
+ * @throws PreventedException On errors that prevented authentication from occurring. Implementing classes SHOULD + * take care to populate the cause, where applicable, with the error that prevented authentication. + */ + HandlerResult authenticate(Credential credential) throws GeneralSecurityException, PreventedException; + + + /** + * Determines whether the handler has the capability to authenticate the given credential. In practical terms, + * the {@link #authenticate(Credential)} method MUST be capable of processing a given credential if + * supports returns true on the same credential. + * + * @param credential The credential to check. + * + * @return True if the handler supports the Credential, false otherwise. + */ + boolean supports(Credential credential); + + + /** + * Gets a unique name for this authentication handler within the Spring context that contains it. + * For implementations that allow setting a unique name, deployers MUST take care to ensure that every + * handler instance has a unique name. + * + * @return Unique name within a Spring context. + */ + String getName(); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationManager.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationManager.java new file mode 100644 index 000000000000..dc7b2775d6ec --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationManager.java @@ -0,0 +1,49 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +/** + * Authenticates one or more credentials. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * + * @since 3.0.0 + */ +public interface AuthenticationManager { + + /** Authentication method attribute name. **/ + String AUTHENTICATION_METHOD_ATTRIBUTE = "authenticationMethod"; + + /** + * Authenticates the provided credentials. On success, an {@link Authentication} object + * is returned containing metadata about the result of each authenticated credential. + * Note that a particular implementation may require some or all credentials to be + * successfully authenticated. Failure to authenticate is considered an exceptional case, and + * an AuthenticationException is thrown. + * + * @param credentials One or more credentials to authenticate. + * + * @return Authentication object on success that contains metadata about credentials that were authenticated. + * + * @throws AuthenticationException On authentication failure. The exception contains details + * on each of the credentials that failed to authenticate. + */ + Authentication authenticate(final Credential... credentials) throws AuthenticationException; +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationMetaDataPopulator.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationMetaDataPopulator.java new file mode 100644 index 000000000000..79cb139e6b82 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationMetaDataPopulator.java @@ -0,0 +1,52 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +/** + * An extension point to the Authentication process that allows CAS to provide + * additional attributes related to the overall Authentication (such as + * authentication type) that are specific to the Authentication request versus + * the Principal itself. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * + * @since 3.0.0 + */ +public interface AuthenticationMetaDataPopulator { + + /** + * Adds authentication metadata attributes on successful authentication of the given credential. + * + * @param builder Builder object that temporarily holds authentication metadata. + * @param credential Successfully authenticated credential. + */ + void populateAttributes(AuthenticationBuilder builder, Credential credential); + + /** + * Determines whether the populator has the capability to perform tasks on the given credential. + * In practice, the {@link #populateAttributes(AuthenticationBuilder, Credential)} needs to be able + * to operate on said credentials only if the return result here is true. + * + * @param credential The credential to check. + * @return True if populator supports the Credential, false otherwise. + * @since 4.1.0 + */ + boolean supports(Credential credential); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationPolicy.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationPolicy.java new file mode 100644 index 000000000000..e1513f76bde4 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/AuthenticationPolicy.java @@ -0,0 +1,36 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +/** + * Stategy interface for pluggable authentication security policies. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public interface AuthenticationPolicy { + /** + * Determines whether an authentication event isSatisfiedBy arbitrary security policy. + * + * @param authentication Authentication event to examine for compliance with security policy. + * + * @return True if authentication isSatisfiedBy security policy, false otherwise. + */ + boolean isSatisfiedBy(Authentication authentication); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/ContextualAuthenticationPolicy.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/ContextualAuthenticationPolicy.java new file mode 100644 index 000000000000..51e5886ee3f3 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/ContextualAuthenticationPolicy.java @@ -0,0 +1,34 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +/** + * A stateful authentication policy that is applied using arbitrary contextual information. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public interface ContextualAuthenticationPolicy extends AuthenticationPolicy { + /** + * Gets the context used to evaluate the authentication policy. + * + * @return Context information. + */ + T getContext(); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/ContextualAuthenticationPolicyFactory.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/ContextualAuthenticationPolicyFactory.java new file mode 100644 index 000000000000..2aec8c3feb19 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/ContextualAuthenticationPolicyFactory.java @@ -0,0 +1,40 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +/** + * A factory for producing (stateful) authentication policies based on arbitrary context data. + * This component provides a way to inject stateless factories into components that produce stateful + * authentication policies that can leverage arbitrary contextual information to evaluate security policy. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public interface ContextualAuthenticationPolicyFactory { + + /** + * Creates a contextual (presumably stateful) authentication policy based on provided context data. + * + * @param context Context data used to create an authentication policy. + * + * @return Contextual authentication policy object. The returned object should be assumed to be stateful + * and not thread safe unless explicitly noted otherwise. + */ + ContextualAuthenticationPolicy createPolicy(T context); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/Credential.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/Credential.java new file mode 100644 index 000000000000..742239e3fcdb --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/Credential.java @@ -0,0 +1,46 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +/** + * Describes an authentication credential. Implementations SHOULD also implement {@link CredentialMetaData} if + * no sensitive data is contained in the credential; conversely, implementations MUST NOT implement + * {@link CredentialMetaData} if the credential contains sensitive data (e.g. password, key material). + * + * @author William G. Thompson, Jr. + * @author Marvin S. Addison + * @see CredentialMetaData + * @since 3.0.0 + */ +public interface Credential { + + /** An ID that may be used to indicate the credential identifier is unknown. */ + String UNKNOWN_ID = "unknown"; + + /** + * Gets a credential identifier that is safe to record for logging, auditing, or presentation to the user. + * In most cases this has a natural meaning for most credential types (e.g. username, certificate DN), while + * for others it may be awkward to construct a meaningful identifier. In any case credentials require some means + * of identification for a number of cases and implementers should make a best effor to satisfy that need. + * + * @return Non-null credential identifier. Implementers should return {@link #UNKNOWN_ID} for cases where an ID + * is not readily available or meaningful. + */ + String getId(); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/CredentialMetaData.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/CredentialMetaData.java new file mode 100644 index 000000000000..04d92b657f7d --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/CredentialMetaData.java @@ -0,0 +1,36 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +/** + * Describes a credential provided for authentication. Implementations should expect instances of this type to be + * stored for periods of time equal to the length of the SSO session or longer, which necessitates consideration of + * serialization and security. All implementations MUST be serializable and secure with respect to long-term storage. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public interface CredentialMetaData { + /** + * Gets a unique identifier for the kind of credential this represents. + * + * @return Unique identifier for the given type of credential. + */ + String getId(); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/HandlerResult.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/HandlerResult.java new file mode 100644 index 000000000000..662f57e3c6c4 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/HandlerResult.java @@ -0,0 +1,63 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.authentication; + +import org.jasig.cas.MessageDescriptor; +import org.jasig.cas.authentication.principal.Principal; + +import java.io.Serializable; +import java.util.List; + +/** + * This is {@link HandlerResult} that describes the result of an authentication attempt. + * + * @author Misagh Moayyed + * @since 4.1.0 + */ +public interface HandlerResult extends Serializable { + + /** + * Gets handler name. + * + * @return the handler name + */ + String getHandlerName(); + + /** + * Gets credential meta data. + * + * @return the credential meta data + */ + CredentialMetaData getCredentialMetaData(); + + /** + * Gets principal. + * + * @return the principal + */ + Principal getPrincipal(); + + /** + * Gets warnings. + * + * @return the warnings + */ + List getWarnings(); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/PreventedException.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/PreventedException.java new file mode 100644 index 000000000000..e45cda094eed --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/PreventedException.java @@ -0,0 +1,50 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +/** + * Describes an error condition where authentication was prevented for some reason, e.g. communication + * error with back-end authentication store. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class PreventedException extends Exception { + + private static final long serialVersionUID = 4702274165911620708L; + + /** + * Creates a new instance with the exception that prevented authentication. + * + * @param cause Error that prevented authentication. + */ + public PreventedException(final Throwable cause) { + super(cause); + } + + /** + * Creates a new instance with an explanatory message and the exception that prevented authentication. + * + * @param message Descriptive error message. + * @param cause Error that prevented authentication. + */ + public PreventedException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/RememberMeCredential.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/RememberMeCredential.java new file mode 100644 index 000000000000..5407b6dfde77 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/RememberMeCredential.java @@ -0,0 +1,50 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +/** + * Credential that wish to handle remember me scenarios need + * to implement this class. + * + * @author Scott Battaglia + * @since 3.2.1 + * + */ +public interface RememberMeCredential extends Credential { + + /** Authentication attribute name for remember-me. **/ + String AUTHENTICATION_ATTRIBUTE_REMEMBER_ME = "org.jasig.cas.authentication.principal.REMEMBER_ME"; + + /** Request parameter name. **/ + String REQUEST_PARAMETER_REMEMBER_ME = "rememberMe"; + + /** + * Checks if remember-me is enabled. + * + * @return true, if remember me + */ + boolean isRememberMe(); + + /** + * Sets the remember me. + * + * @param rememberMe the new remember me + */ + void setRememberMe(boolean rememberMe); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/RootCasException.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/RootCasException.java new file mode 100644 index 000000000000..e670b511d888 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/RootCasException.java @@ -0,0 +1,92 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import javax.validation.constraints.NotNull; + +/** + * Generic CAS exception that sits at the top of the exception hierarchy. Provides + * unified logic around retrieval and configuration of exception codes that may be + * mapped inside an external resource bundle for internationalization of error messages. + * + * @author Misagh Moayyed + * @see org.jasig.cas.authentication.handler.AuthenticationException + * @see org.jasig.cas.ticket.TicketException + * @since 4.0.0 + */ +public abstract class RootCasException extends Exception { + + private static final long serialVersionUID = -2384466176716541689L; + + /** The code description of the exception. */ + private final String code; + + /** + * Constructor that takes a code description of the error along with the exception + * msg generally for logging purposes. These codes normally have a corresponding + * entries in the messages file for the internationalization of error messages. + * + * @param code the code to describe what type of exception this is. + */ + public RootCasException(@NotNull final String code) { + this.code = code; + } + + /** + * Constructs a new exception with the code identifying the exception + * and the error message. + * + * @param code the code to describe what type of exception this is. + * @param msg The error message associated with this exception for additional logging purposes. + */ + public RootCasException(@NotNull final String code, final String msg) { + super(msg); + this.code = code; + } + + /** + * Constructs a new exception with the code identifying the exception + * and the original throwable. + * + * @param code the code to describe what type of exception this is. + * @param throwable the original exception we are chaining. + */ + public RootCasException(@NotNull final String code, final Throwable throwable) { + super(throwable); + this.code = code; + } + + /** + * @return Returns the code. If there is a chained exception it recursively + * calls getCode() on the cause of the chained exception rather than the returning + * the code itself. + */ + public final String getCode() { + final Throwable cause = this.getCause(); + if (cause instanceof RootCasException) { + return ((RootCasException) cause).getCode(); + } + return this.code; + } + + @Override + public String toString() { + return this.getCode(); + } +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/handler/PasswordEncoder.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/handler/PasswordEncoder.java new file mode 100644 index 000000000000..888e7cdc22ac --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/handler/PasswordEncoder.java @@ -0,0 +1,47 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +/** + * Interface to provide a standard way to translate a plaintext password into a + * different representation of that password so that the password may be + * compared with the stored encrypted password without having to decode the + * encrypted password. + *

+ * PasswordEncoders are useful because often the stored passwords are encoded + * with a one way hash function which makes them almost impossible to decode. + * + * @author Scott Battaglia + + * @since 3.0.0 + *

+ * This is a published and supported CAS Server 3 API. + *

+ */ +public interface PasswordEncoder { + + /** + * Method that actually performs the transformation of the plaintext + * password into the encrypted password. + * + * @param password the password to translate + * @return the transformed version of the password + */ + String encode(String password); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/handler/PrincipalNameTransformer.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/handler/PrincipalNameTransformer.java new file mode 100644 index 000000000000..39e765a6f9f2 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/handler/PrincipalNameTransformer.java @@ -0,0 +1,46 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + + +/** + * Strategy pattern component for transforming principal names in the authentication pipeline. + * + * @author Howard Gilbert + * @since 3.3.6 + */ +public interface PrincipalNameTransformer { + + /** + * Transform the string typed into the login form into a tentative Principal Name to be + * validated by a specific type of Authentication Handler. + * + *

The Principal Name eventually assigned by the PrincipalResolver may + * be unqualified ("AENewman"). However, validation of the Principal name against a + * particular backend source represented by a particular Authentication Handler may + * require transformation to a temporary fully qualified format such as + * AENewman@MAD.DCCOMICS.COM or MAD\AENewman. After validation, this form of the + * Principal name is discarded in favor of the choice made by the Resolver. + * + * @param formUserId The raw userid typed into the login form + * @return the string that the Authentication Handler should lookup in the backend system + */ + String transform(String formUserId); +} + diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/PersistentIdGenerator.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/PersistentIdGenerator.java new file mode 100644 index 000000000000..5da601b243d6 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/PersistentIdGenerator.java @@ -0,0 +1,39 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import java.io.Serializable; + +/** + * Generates a unique consistent Id based on the principal. + * + * @author Scott Battaglia + * @since 3.1 + */ +public interface PersistentIdGenerator extends Serializable { + + /** + * Generates a PersistentId based on some algorithm plus the principal. + * + * @param principal the principal to generate the id for. + * @param service the service for which the id may be generated. + * @return the generated persistent id. + */ + String generate(Principal principal, Service service); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/Principal.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/Principal.java new file mode 100644 index 000000000000..dec315fbaf1b --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/Principal.java @@ -0,0 +1,52 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import java.io.Serializable; +import java.util.Map; + +/** + * Generic concept of an authenticated thing. Examples include a person or a + * service. + *

+ * The implementation SimplePrincipal just contains the Id property. More + * complex Principal objects may contain additional information that are + * meaningful to the View layer but are generally transparent to the rest of + * CAS. + *

+ * + * @author Scott Battaglia + * @since 3.0.0 + *

+ * This is a published and supported CAS Server 3 API. + *

+ */ +public interface Principal extends Serializable { + + /** + * @return the unique id for the Principal + */ + String getId(); + + /** + * + * @return the map of configured attributes for this principal + */ + Map getAttributes(); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/PrincipalAttributesRepository.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/PrincipalAttributesRepository.java new file mode 100644 index 000000000000..cf6a32e7f3d6 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/PrincipalAttributesRepository.java @@ -0,0 +1,44 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import java.io.Serializable; +import java.util.Map; + +/** + * Defines operations required for retrieving principal attributes. + * Acts as a proxy between the external attribute source and CAS, + * executing such as additional processing or caching on the set + * of retrieved attributes. Implementations may simply decide to + * do nothing on the set of attributes that the principal carries + * or they may attempt to refresh them from the source, etc. + * @author Misagh Moayyed + * @see org.jasig.cas.authentication.principal.PrincipalFactory + * @since 4.1 + */ +public interface PrincipalAttributesRepository extends Serializable { + + /** + * Gets attributes for the given principal id. + * + * @param p the principal whose attributes need to be retrieved. + * @return the attributes + */ + Map getAttributes(Principal p); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/PrincipalFactory.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/PrincipalFactory.java new file mode 100644 index 000000000000..a9613b4d47d3 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/PrincipalFactory.java @@ -0,0 +1,47 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import java.io.Serializable; +import java.util.Map; + +/** + * Defines operations to create principals. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public interface PrincipalFactory extends Serializable { + /** + * Create principal. + * + * @param id the id + * @return the principal + */ + Principal createPrincipal(String id); + + /** + * Create principal along with its attributes. + * + * @param id the id + * @param attributes the attributes + * @return the principal + */ + Principal createPrincipal(String id, Map attributes); + +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/PrincipalResolver.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/PrincipalResolver.java new file mode 100644 index 000000000000..7a16a71fc7ca --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/PrincipalResolver.java @@ -0,0 +1,57 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import org.jasig.cas.authentication.Credential; + +/** + * Resolves a {@link Principal} from a {@link Credential} using an arbitrary strategy. + * Since a {@link Principal} requires an identifier at a minimum, the simplest strategy to produce a principal + * is to simply copy {@link org.jasig.cas.authentication.Credential#getId()} onto + * {@link org.jasig.cas.authentication.principal.Principal#getId()}. Resolvers commonly query one or more data sources + * to obtain attributes such as affiliations, group membership, display name, and email. The data source(s) may also + * provide an alternate identifier mapped by the credential identifier. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @see Principal + * @see Credential + * @since 4.0.0 + */ +public interface PrincipalResolver { + + /** + * Resolves a principal from the given credential using an arbitrary strategy. + * + * @param credential Source credential. + * + * @return Resolved principal, or null if the principal could not be resolved. + */ + Principal resolve(Credential credential); + + /** + * Determines whether this instance supports principal resolution from the given credential. This method SHOULD + * be called prior to {@link #resolve(org.jasig.cas.authentication.Credential)}. + * + * @param credential The credential to check for support. + * + * @return True if credential is supported, false otherwise. + */ + boolean supports(Credential credential); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/Response.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/Response.java new file mode 100644 index 000000000000..678b3d200782 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/Response.java @@ -0,0 +1,62 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.authentication.principal; + +import java.io.Serializable; +import java.util.Map; + +/** + * This is {@link Response} that is outputted by each service principal. + * + * @author Misagh Moayyed + * @since 4.1.0 + */ +public interface Response extends Serializable { + /** An enumeration of different response types. */ + public static enum ResponseType { + + /** The post. */ + POST, + + /** The redirect. */ + REDIRECT + } + + /** + * Gets attributes. + * + * @return the attributes + */ + Map getAttributes(); + + /** + * Gets response type. + * + * @return the response type + */ + ResponseType getResponseType(); + + /** + * Gets url. + * + * @return the url + */ + String getUrl(); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/Service.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/Service.java new file mode 100644 index 000000000000..aa6e9fef9c27 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/Service.java @@ -0,0 +1,49 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +/** + * Marker interface for Services. Services are generally either remote + * applications utilizing CAS or applications that principals wish to gain + * access to. In most cases this will be some form of web application. + * + * @author William G. Thompson, Jr. + * @author Scott Battaglia + * @since 3.0.0 + *

+ * This is a published and supported CAS Server 3 API. + *

+ */ +public interface Service extends Principal { + + /** + * Sets the principal. + * + * @param principal the new principal + */ + void setPrincipal(Principal principal); + + /** + * Whether the services matches another. + * + * @param service the service + * @return true, if successful + */ + boolean matches(Service service); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/SingleLogoutService.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/SingleLogoutService.java new file mode 100644 index 000000000000..060e9e55e10f --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/SingleLogoutService.java @@ -0,0 +1,42 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +/** + * Define a service which support single logout. + * + * @author Jerome Leleu + * @since 4.0.0 + */ +public interface SingleLogoutService extends WebApplicationService { + + /** + * Return if the service is already logged out. + * + * @return if the service is already logged out. + */ + boolean isLoggedOutAlready(); + + /** + * Set if the service is already logged out. + * + * @param loggedOutAlready if the service is already logged out. + */ + void setLoggedOutAlready(boolean loggedOutAlready); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/WebApplicationService.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/WebApplicationService.java new file mode 100644 index 000000000000..28fec752079b --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/principal/WebApplicationService.java @@ -0,0 +1,51 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +/** + * Represents a service using CAS that comes from the web. + * + * @author Scott Battaglia + * @since 3.1 + */ +public interface WebApplicationService extends Service { + + /** + * Constructs the url to redirect the service back to. + * + * @param ticketId the service ticket to provide to the service. + * @return the redirect url. + */ + Response getResponse(String ticketId); + + /** + * Retrieves the artifact supplied with the service. May be null. + * + * @return the artifact if it exists, null otherwise. + */ + String getArtifactId(); + + /** + * Return the original url provided (as service or targetService request parameter). + * Used to reconstruct the redirect url. + * + * @return the original url provided. + */ + String getOriginalUrl(); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/authentication/support/CasAttributeEncoder.java b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/support/CasAttributeEncoder.java new file mode 100644 index 000000000000..c4f32cc34bd3 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/authentication/support/CasAttributeEncoder.java @@ -0,0 +1,54 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.authentication.support; + +import org.jasig.cas.authentication.principal.Service; + +import java.util.Map; + +/** + * An encoder that defines how a CAS attribute + * is to be encoded and signed in the CAS + * validation response. The collection of + * attributes should not be mangled with and + * filtered. All attributes will be released. + * It is up to the implementations + * to decide which attribute merits encrypting. + * + * @author Misagh Moayyed + * @since 4.1 + */ +public interface CasAttributeEncoder { + + /** + * Encodes attributes that are ready to be released. + * Specifically, this method tries to ensure that the + * PGT and the credential password are correctly encrypted + * before they are released. Attributes should not be filtered + * and removed and it is assumed that all will be returned + * back to the service. + * @param attributes The attribute collection that is ready to be released + * @param service the requesting service for which attributes are to be encoded + * @return collection of attributes after encryption ready for release. + * @since 4.1 + */ + Map encodeAttributes(Map attributes, Service service); + +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/logout/LogoutManager.java b/cas-server-core-api/src/main/java/org/jasig/cas/logout/LogoutManager.java new file mode 100644 index 000000000000..38d78b452462 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/logout/LogoutManager.java @@ -0,0 +1,48 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.logout; + +import java.util.List; + +import org.jasig.cas.ticket.TicketGrantingTicket; + +/** + * A logout manager handles the Single Log Out process. + * + * @author Jerome Leleu + * @since 4.0.0 + */ +public interface LogoutManager { + + /** + * Perform a back channel logout for a given ticket granting ticket and returns all the logout requests. + * + * @param ticket a given ticket granting ticket. + * @return all logout requests. + */ + List performLogout(TicketGrantingTicket ticket); + + /** + * Create a logout message for front channel logout. + * + * @param logoutRequest the logout request. + * @return a front SAML logout message. + */ + String createFrontChannelLogoutMessage(LogoutRequest logoutRequest); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/logout/LogoutMessageCreator.java b/cas-server-core-api/src/main/java/org/jasig/cas/logout/LogoutMessageCreator.java new file mode 100644 index 000000000000..bcd366c85c06 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/logout/LogoutMessageCreator.java @@ -0,0 +1,35 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.logout; + +/** + * Contract that defines the format of the logout message sent to a client to indicate + * that an SSO session has terminated. + * @author Misagh Moayyed + * @since 4.0.0 + */ +public interface LogoutMessageCreator { + /** + * Builds the logout message to be sent. + * + * @param request the request + * @return the message. Message may or may not be encoded. + */ + String create(LogoutRequest request); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/logout/LogoutRequest.java b/cas-server-core-api/src/main/java/org/jasig/cas/logout/LogoutRequest.java new file mode 100644 index 000000000000..7752648972aa --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/logout/LogoutRequest.java @@ -0,0 +1,69 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.logout; + +import org.jasig.cas.authentication.principal.SingleLogoutService; + +import java.io.Serializable; +import java.net.URL; + +/** + * Identifies a CAS logout request and its properties. + * + * @author Misagh Moayyed + * @since 4.1.0 + */ +public interface LogoutRequest extends Serializable { + /** + * Gets status of the request. + * + * @return the status + */ + LogoutRequestStatus getStatus(); + + /** + * Sets status of the request. + * + * @param status the status + */ + void setStatus(LogoutRequestStatus status); + + /** + * Gets ticket id. + * + * @return the ticket id + */ + String getTicketId(); + + /** + * Gets service. + * + * @return the service + */ + SingleLogoutService getService(); + + /** + * Gets logout url. + * + * @return the logout url + */ + URL getLogoutUrl(); + +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/logout/LogoutRequestStatus.java b/cas-server-core-api/src/main/java/org/jasig/cas/logout/LogoutRequestStatus.java new file mode 100644 index 000000000000..bbbfcb00d117 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/logout/LogoutRequestStatus.java @@ -0,0 +1,34 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.logout; + +/** + * Define the status of a logout request. + * + * @author Jerome Leleu + * @since 4.0.0 + */ +public enum LogoutRequestStatus { + /** the logout request has not been performed. */ + NOT_ATTEMPTED, + /** the logout request has failed. */ + FAILURE, + /** the logout request has successed. */ + SUCCESS +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/monitor/CacheStatistics.java b/cas-server-core-api/src/main/java/org/jasig/cas/monitor/CacheStatistics.java new file mode 100644 index 000000000000..b96ea118bdf4 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/monitor/CacheStatistics.java @@ -0,0 +1,76 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +/** + * Describes the simplest set of cache statistics that are meaningful for health monitoring. + * + * @author Marvin S. Addison + * @since 3.5.1 + */ +public interface CacheStatistics { + + /** + * Gets the current size of the cache in a unit specific to the cache being monitored (e.g. bytes, items, etc). + * + * @return Current cache size. + */ + long getSize(); + + + /** + * Gets the current capacity of the cache in a unit specific to the cache being monitored (e.g. bytes, items, etc). + * + * @return Current cache capacity. + */ + long getCapacity(); + + + /** + * Gets the number of items evicted from the cache in order to make space for new items. + * + * @return Eviction count. + */ + long getEvictions(); + + + /** + * Gets the percent free capacity remaining in the cache. + * + * @return Percent of space/capacity free. + */ + int getPercentFree(); + + + /** + * Gets a descriptive name of the cache instance for which statistics apply. + * + * @return Name of cache instance/host to which statistics apply. + */ + String getName(); + + + /** + * Writes a string representation of cache statistics to the given string builder. + * + * @param builder String builder to which string representation is appended. + */ + void toString(StringBuilder builder); + +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/monitor/Monitor.java b/cas-server-core-api/src/main/java/org/jasig/cas/monitor/Monitor.java new file mode 100644 index 000000000000..8d6be7be9218 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/monitor/Monitor.java @@ -0,0 +1,44 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +/** + * A monitor observes a resource and reports its status. + * + * @author Marvin S. Addison + * @param the generic type of the monitor + * @since 3.5.0 + */ +public interface Monitor { + + /** + * Gets the name of the monitor. + * + * @return Monitor name. + */ + String getName(); + + + /** + * Observes the monitored resource and reports the status. + * + * @return Status of monitored resource. + */ + S observe(); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/monitor/Status.java b/cas-server-core-api/src/main/java/org/jasig/cas/monitor/Status.java new file mode 100644 index 000000000000..ea851d57aee5 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/monitor/Status.java @@ -0,0 +1,92 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +/** + * Describes a generic status condition. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +public class Status { + + /** Generic UNKNOWN status. */ + public static final Status UNKNOWN = new Status(StatusCode.UNKNOWN); + + /** Generic OK status. */ + public static final Status OK = new Status(StatusCode.OK); + + /** Generic INFO status. */ + public static final Status INFO = new Status(StatusCode.INFO); + + /** Generic WARN status. */ + public static final Status WARN = new Status(StatusCode.WARN); + + /** Generic ERROR status. */ + public static final Status ERROR = new Status(StatusCode.ERROR); + + /** Status code. */ + private final StatusCode code; + + /** Human-readable status description. */ + private final String description; + + + /** + * Creates a new status object with the given code. + * + * @param code Status code. + * + * @see #getCode() + */ + public Status(final StatusCode code) { + this(code, null); + } + + + /** + * Creates a new status object with the given code. + * + * @param code Status code. + * @param desc Human-readable status description. + * + * @see #getCode() + */ + public Status(final StatusCode code, final String desc) { + this.code = code; + this.description = desc; + } + + /** + * Gets the status code. + * + * @return Status code. + */ + public StatusCode getCode() { + return this.code; + } + + + /** + * @return Human-readable description of status. + */ + public String getDescription() { + return this.description; + } +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/monitor/StatusCode.java b/cas-server-core-api/src/main/java/org/jasig/cas/monitor/StatusCode.java new file mode 100644 index 000000000000..8c8fdcaf0e06 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/monitor/StatusCode.java @@ -0,0 +1,66 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +/** + * Monitor status code inspired by HTTP status codes. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +public enum StatusCode { + + /** The error. */ + ERROR(500), + + /** The warn. */ + WARN(400), + + /** The info. */ + INFO(300), + + /** The ok. */ + OK(200), + + /** The unknown. */ + UNKNOWN(100); + + /** Status code numerical value. */ + private final int value; + + + /** + * Creates a new instance with the given numeric value. + * + * @param numericValue Numeric status code value. + */ + StatusCode(final int numericValue) { + this.value = numericValue; + } + + + /** + * Gets the numeric value of the status code. Higher values describe more severe conditions. + * + * @return Numeric status code value. + */ + public int value() { + return this.value; + } +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/monitor/TicketRegistryState.java b/cas-server-core-api/src/main/java/org/jasig/cas/monitor/TicketRegistryState.java new file mode 100644 index 000000000000..26e36e62e5e0 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/monitor/TicketRegistryState.java @@ -0,0 +1,46 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +/** + * Describes important state information that may be optionally exposed by + * {@link org.jasig.cas.ticket.registry.TicketRegistry} components that might + * be of interest to monitors. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +public interface TicketRegistryState { + /** + * Computes the number of SSO sessions stored in the ticket registry. + * + * @return Number of ticket-granting tickets in the registry at time of invocation + * or {@link Integer#MIN_VALUE} if unknown. + */ + int sessionCount(); + + + /** + * Computes the number of service tickets stored in the ticket registry. + * + * @return Number of service tickets in the registry at time of invocation + * or {@link Integer#MIN_VALUE} if unknown. + */ + int serviceTicketCount(); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/services/AttributeFilter.java b/cas-server-core-api/src/main/java/org/jasig/cas/services/AttributeFilter.java new file mode 100644 index 000000000000..49cd62f29c79 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/services/AttributeFilter.java @@ -0,0 +1,40 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import java.io.Serializable; +import java.util.Map; + +/** + * Defines the general contract of the attribute release policy for a registered service. + * An instance of this attribute filter may determine how principal/global attributes are translated to a + * map of attributes that may be released for a registered service. + * + * @author Misagh Moayyed + * @since 4.1.0 + */ +public interface AttributeFilter extends Serializable { + /** + * Filters the received principal attributes for the given registered service. + * + * @param givenAttributes the map for the original given attributes + * @return a map that contains the filtered attributes. + */ + Map filter(Map givenAttributes); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/services/AttributeReleasePolicy.java b/cas-server-core-api/src/main/java/org/jasig/cas/services/AttributeReleasePolicy.java new file mode 100644 index 000000000000..62b1e348c0bb --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/services/AttributeReleasePolicy.java @@ -0,0 +1,62 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import java.io.Serializable; +import java.util.Map; + +import org.jasig.cas.authentication.principal.Principal; + +/** + * The release policy that decides how attributes are to be released for a given service. + * Each policy has the ability to apply an optional filter. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public interface AttributeReleasePolicy extends Serializable { + + /** + * Is authorized to release credential password? + * + * @return the boolean + */ + boolean isAuthorizedToReleaseCredentialPassword(); + + /** + * Is authorized to release proxy granting ticket? + * + * @return the boolean + */ + boolean isAuthorizedToReleaseProxyGrantingTicket(); + + /** + * Sets the attribute filter. + * + * @param filter the new attribute filter + */ + void setAttributeFilter(AttributeFilter filter); + + /** + * Gets the attributes, having applied the filter. + * + * @param p the principal that contains the resolved attributes + * @return the attributes + */ + Map getAttributes(Principal p); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/services/LogoutType.java b/cas-server-core-api/src/main/java/org/jasig/cas/services/LogoutType.java new file mode 100644 index 000000000000..92491b18cafa --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/services/LogoutType.java @@ -0,0 +1,40 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +/** + * Enumeration of the logout type. + * + * @author Jerome Leleu + * @since 4.0.0 + */ +public enum LogoutType { + /** + * For no SLO. + */ + NONE, + /** + * For back channel SLO. + */ + BACK_CHANNEL, + /** + * For front channel SLO. + */ + FRONT_CHANNEL +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/services/RegisteredService.java b/cas-server-core-api/src/main/java/org/jasig/cas/services/RegisteredService.java new file mode 100644 index 000000000000..efdb4c6ce529 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/services/RegisteredService.java @@ -0,0 +1,183 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.jasig.cas.authentication.principal.Service; + +import java.io.Serializable; +import java.net.URL; +import java.util.Set; + +/** + * Interface for a service that can be registered by the Services Management + * interface. + * + * @author Scott Battaglia + * @since 3.1 + */ +public interface RegisteredService extends Cloneable, Serializable { + + /** Initial ID value of newly created (but not persisted) registered service. **/ + long INITIAL_IDENTIFIER_VALUE = -Long.MAX_VALUE; + + /** + * Get the proxy policy rules for this service. + * + * @return the proxy policy + */ + RegisteredServiceProxyPolicy getProxyPolicy(); + + /** + * The unique identifier for this service. + * + * @return the unique identifier for this service. + */ + String getServiceId(); + + /** + * The numeric identifier for this service. Implementations + * are expected to initialize the id with the value of {@link #INITIAL_IDENTIFIER_VALUE}. + * @return the numeric identifier for this service. + */ + long getId(); + + /** + * Returns the name of the service. + * + * @return the name of the service. + */ + String getName(); + + /** + * Returns a short theme name. Services do not need to have unique theme + * names. + * + * @return the theme name associated with this service. + */ + String getTheme(); + + /** + * Returns the description of the service. + * + * @return the description of the service. + */ + String getDescription(); + + /** + * Gets the relative evaluation order of this service when determining + * matches. + * @return Evaluation order relative to other registered services. + * Services with lower values will be evaluated for a match before others. + */ + int getEvaluationOrder(); + + /** + * Sets the relative evaluation order of this service when determining + * matches. + * @param evaluationOrder the service evaluation order + */ + void setEvaluationOrder(int evaluationOrder); + + /** + * Get the name of the attribute this service prefers to consume as username. + * @return an instance of {@link RegisteredServiceUsernameAttributeProvider} + */ + RegisteredServiceUsernameAttributeProvider getUsernameAttributeProvider(); + + /** + * Gets the set of handler names that must successfully authenticate credentials in order to access the service. + * An empty set indicates that there are no requirements on particular authentication handlers; any will suffice. + * + * @return Non-null set of required handler names. + */ + Set getRequiredHandlers(); + + /** + * Gets the access strategy that decides whether this registered + * service is able to proceed with authentication requests. + * + * @return the access strategy + */ + RegisteredServiceAccessStrategy getAccessStrategy(); + + /** + * Returns whether the service matches the registered service. + *

Note, as of 3.1.2, matches are case insensitive. + * + * @param service the service to match. + * @return true if they match, false otherwise. + */ + boolean matches(Service service); + + /** + * Clone this service. + * + * @return the registered service + * @throws CloneNotSupportedException the clone not supported exception + */ + RegisteredService clone() throws CloneNotSupportedException; + + /** + * Returns the logout type of the service. + * + * @return the logout type of the service. + */ + LogoutType getLogoutType(); + + /** + * Gets the attribute filtering policy to determine + * how attributes are to be filtered and released for + * this service. + * + * @return the attribute release policy + */ + AttributeReleasePolicy getAttributeReleasePolicy(); + + /** + * Gets the logo image associated with this service. + * The image mostly is served on the user interface + * to identify this requesting service during authentication. + * @return URL of the image + * @since 4.1 + */ + URL getLogo(); + + /** + * Identifies the logout url that that will be invoked + * upon sending single-logout callback notifications. + * This is an optional setting. When undefined, the service + * url as is defined by {@link #getServiceId()} will be used + * to handle logout invocations. + * @return the logout url for this service + * @since 4.1 + */ + URL getLogoutUrl(); + + /** + * Gets the public key associated with this service + * that is used to authorize the request by + * encrypting certain elements and attributes in + * the CAS validation protocol response, such as + * the PGT. + * @return the public key instance used to authorize the request + * @since 4.1 + */ + RegisteredServicePublicKey getPublicKey(); + +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/services/RegisteredServiceAccessStrategy.java b/cas-server-core-api/src/main/java/org/jasig/cas/services/RegisteredServiceAccessStrategy.java new file mode 100644 index 000000000000..62dad820bbc3 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/services/RegisteredServiceAccessStrategy.java @@ -0,0 +1,60 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.services; + +import java.io.Serializable; +import java.util.Map; + +/** + * This is {@link RegisteredServiceAccessStrategy} + * that can decide if a service is recognized and authorized to participate + * in the CAS protocol flow during authentication/validation events. + * + * @author Misagh Moayyed mmoayyed@unicon.net + * @since 4.1 + */ +public interface RegisteredServiceAccessStrategy extends Serializable { + + /** + * Verify is the service is enabled and recognized by CAS. + * + * @return true/false if service is enabled + */ + boolean isServiceAccessAllowed(); + + /** + * Assert that the service can participate in sso. + * + * @return true/false if service can participate in sso + */ + boolean isServiceAccessAllowedForSso(); + + /** + * Verify authorization policy by checking the pre-configured rules + * that may depend on what the principal might be carrying. + * + * @param principalAttributes the principal attributes. Rather than passing the principal + * directly, we are only allowing principal attributes + * given they may be coming from a source external to the principal + * itself. (Cached principal attributes, etc) + * @return true/false if service access can be granted to principal + */ + boolean doPrincipalAttributesAllowServiceAccess(Map principalAttributes); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/services/RegisteredServiceProxyPolicy.java b/cas-server-core-api/src/main/java/org/jasig/cas/services/RegisteredServiceProxyPolicy.java new file mode 100644 index 000000000000..87603792ebf1 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/services/RegisteredServiceProxyPolicy.java @@ -0,0 +1,53 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import java.io.Serializable; +import java.net.URL; + +/** + * Defines the proxying policy for a registered service. + * While a service may be allowed proxying on a general level, + * it may still want to restrict who is authorizes to receive + * the proxy granting ticket. This interface defines the behavior + * for both options. + * + * @author Misagh Moayyed + * @since 4.1.0 + */ +public interface RegisteredServiceProxyPolicy extends Serializable { + + /** + * Determines whether the service is allowed proxy + * capabilities. + * + * @return true, if is allowed to proxy + */ + boolean isAllowedToProxy(); + + /** + * Determines if the given proxy callback + * url is authorized and allowed to + * request proxy access. + * + * @param pgtUrl the pgt url + * @return true, if url allowed. + */ + boolean isAllowedProxyCallbackUrl(URL pgtUrl); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/services/RegisteredServicePublicKey.java b/cas-server-core-api/src/main/java/org/jasig/cas/services/RegisteredServicePublicKey.java new file mode 100644 index 000000000000..bbd5e9ff4113 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/services/RegisteredServicePublicKey.java @@ -0,0 +1,52 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.services; + +import java.security.PublicKey; + +/** + * Represents a public key for a CAS registered service. + * @author Misagh Moayyed + * @since 4.1 + */ +public interface RegisteredServicePublicKey { + + /** + * Gets location to the public key file. + * + * @return the location + */ + String getLocation(); + + /** + * Gets algorithm for the public key. + * + * @return the algorithm + */ + String getAlgorithm(); + + /** + * Create instance. + * + * @return the public key + * @throws Exception the exception thrown if the public key cannot be created + */ + PublicKey createInstance() throws Exception; +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/services/RegisteredServiceUsernameAttributeProvider.java b/cas-server-core-api/src/main/java/org/jasig/cas/services/RegisteredServiceUsernameAttributeProvider.java new file mode 100644 index 000000000000..d9e14d39596d --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/services/RegisteredServiceUsernameAttributeProvider.java @@ -0,0 +1,41 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.Service; + +import java.io.Serializable; + +/** + * Strategy interface to define what username attribute should + * be returned for a given registered service. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public interface RegisteredServiceUsernameAttributeProvider extends Serializable { + /** + * Resolve the username that is to be returned to CAS clients. + * + * @param principal the principal + * @param service the service for which attribute should be calculated + * @return the username value configured for this service + */ + String resolveUsername(Principal principal, Service service); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/services/ReloadableServicesManager.java b/cas-server-core-api/src/main/java/org/jasig/cas/services/ReloadableServicesManager.java new file mode 100644 index 000000000000..c919908b3769 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/services/ReloadableServicesManager.java @@ -0,0 +1,37 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +/** + * Interface to allow for ServicesManagers to attempt to reload their list of + * services. + * + * @author Scott Battaglia + * @since 3.1 + */ +public interface ReloadableServicesManager extends ServicesManager { + + /** + * Inform the ServicesManager to reload its list of services if its cached + * them. Note that this is a suggestion and that ServicesManagers are free + * to reload whenever they want. + */ + void reload(); + +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/services/ServiceRegistryDao.java b/cas-server-core-api/src/main/java/org/jasig/cas/services/ServiceRegistryDao.java new file mode 100644 index 000000000000..f6aabc9d3704 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/services/ServiceRegistryDao.java @@ -0,0 +1,62 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import java.util.List; + +/** + * Registry of all RegisteredServices. + * + * @author Scott Battaglia + + * @since 3.1 + */ +public interface ServiceRegistryDao { + + /** + * Persist the service in the data store. + * + * @param registeredService the service to persist. + * @return the updated RegisteredService. + */ + RegisteredService save(RegisteredService registeredService); + + /** + * Remove the service from the data store. + * + * @param registeredService the service to remove. + * @return true if it was removed, false otherwise. + */ + boolean delete(RegisteredService registeredService); + + /** + * Retrieve the services from the data store. + * + * @return the collection of services. + */ + List load(); + + /** + * Find service by the numeric id. + * + * @param id the id + * @return the registered service + */ + RegisteredService findServiceById(long id); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/services/ServicesManager.java b/cas-server-core-api/src/main/java/org/jasig/cas/services/ServicesManager.java new file mode 100644 index 000000000000..611a8e700e83 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/services/ServicesManager.java @@ -0,0 +1,81 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import java.util.Collection; + +import org.jasig.cas.authentication.principal.Service; + +/** + * Manages the storage, retrieval, and matching of Services wishing to use CAS + * and services that have been registered with CAS. + * + * @author Scott Battaglia + + * @since 3.1 + */ +public interface ServicesManager { + + /** + * Register a service with CAS, or update an existing an entry. + * + * @param registeredService the RegisteredService to update or add. + * @return newly persisted RegisteredService instance + */ + RegisteredService save(RegisteredService registeredService); + + /** + * Delete the entry for this RegisteredService. + * + * @param id the id of the registeredService to delete. + * @return the registered service that was deleted, null if there was none. + */ + RegisteredService delete(long id); + + /** + * Find a RegisteredService by matching with the supplied service. + * + * @param service the service to match with. + * @return the RegisteredService that matches the supplied service. + */ + RegisteredService findServiceBy(Service service); + + /** + * Find a RegisteredService by matching with the supplied id. + * + * @param id the id to match with. + * @return the RegisteredService that matches the supplied service. + */ + RegisteredService findServiceBy(long id); + + /** + * Retrieve the collection of all registered services. + * + * @return the collection of all services. + */ + Collection getAllServices(); + + /** + * Convenience method to let one know if a service exists in the data store. + * + * @param service the service to check. + * @return true if it exists, false otherwise. + */ + boolean matchesExistingService(Service service); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/ticket/ExpirationPolicy.java b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/ExpirationPolicy.java new file mode 100644 index 000000000000..e04eb146e5ce --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/ExpirationPolicy.java @@ -0,0 +1,45 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket; + +import java.io.Serializable; + +/** + * Strategy that determines if the ticket is expired. Implementations of the + * Expiration Policy define their own rules on what they consider an expired + * Ticket to be. + * + * @author Scott Battaglia + * + *

+ * This is a published and supported CAS Server 3 API. + *

+ * @see org.jasig.cas.ticket.Ticket + * @since 3.0.0 + */ +public interface ExpirationPolicy extends Serializable { + + /** + * Method to determine if a Ticket has expired or not, based on the policy. + * + * @param ticketState The snapshot of the current ticket state + * @return true if the ticket is expired, false otherwise. + */ + boolean isExpired(TicketState ticketState); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/ticket/InvalidTicketException.java b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/InvalidTicketException.java new file mode 100644 index 000000000000..6fd3c09b23e7 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/InvalidTicketException.java @@ -0,0 +1,66 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket; + +/** + * TicketException to alert that a Ticket was not found or that it is expired. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public class InvalidTicketException extends TicketException { + + private static final long serialVersionUID = 9141891414482490L; + + /** The code description. */ + private static final String CODE = "INVALID_TICKET"; + + private final String ticketId; + + /** + * Constructs a InvalidTicketException with the default exception code. + * @param ticketId the ticket id that originally caused this exception to be thrown. + */ + public InvalidTicketException(final String ticketId) { + super(CODE); + this.ticketId = ticketId; + } + + /** + * Constructs a InvalidTicketException with the default exception code and + * the original exception that was thrown. + * + * @param throwable the chained exception + * @param ticketId the ticket id that originally caused this exception to be thrown. + */ + public InvalidTicketException(final Throwable throwable, final String ticketId) { + super(CODE, throwable); + this.ticketId = ticketId; + } + + /** + * Returns the ticket id that causes this exception. + * @return the ticket id + * @see InvalidTicketException#ticketId + */ + @Override + public String getMessage() { + return this.ticketId; + } +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/ticket/ServiceTicket.java b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/ServiceTicket.java new file mode 100644 index 000000000000..6010d1ac0f96 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/ServiceTicket.java @@ -0,0 +1,77 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; + +/** + * Interface for a Service Ticket. A service ticket is used to grant access to a + * specific service for a principal. A Service Ticket is generally a one-time + * use ticket. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public interface ServiceTicket extends Ticket { + + /** Prefix generally applied to unique ids generated + * by {@link org.jasig.cas.util.UniqueTicketIdGenerator}. + **/ + String PREFIX = "ST"; + + /** Proxy ticket prefix applied to unique ids + * generated by {@link org.jasig.cas.util.UniqueTicketIdGenerator}. + **/ + String PROXY_TICKET_PREFIX = "PT"; + + /** + * Retrieve the service this ticket was given for. + * + * @return the server. + */ + Service getService(); + + /** + * Determine if this ticket was created at the same time as a + * TicketGrantingTicket. + * + * @return true if it is, false otherwise. + */ + boolean isFromNewLogin(); + + /** + * Attempts to ensure that the service specified matches the service associated with the ticket. + * @param service The incoming service to match this service ticket against. + * @return true, if the match is successful. + */ + boolean isValidFor(Service service); + + /** + * Method to grant a TicketGrantingTicket from this service to the + * authentication. Analogous to the ProxyGrantingTicket. + * + * @param id The unique identifier for this ticket. + * @param authentication The Authentication we wish to grant a ticket for. + * @param expirationPolicy expiration policy associated with this ticket + * @return The ticket granting ticket. + */ + TicketGrantingTicket grantTicketGrantingTicket(String id, + Authentication authentication, ExpirationPolicy expirationPolicy); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/ticket/Ticket.java b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/Ticket.java new file mode 100644 index 000000000000..cea0dfaa297c --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/Ticket.java @@ -0,0 +1,66 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket; + +import java.io.Serializable; + +/** + * Interface for the generic concept of a ticket. + * + * @author Scott Battaglia + + * @since 3.0.0 + */ +public interface Ticket extends Serializable { + + /** + * Method to retrieve the id. + * + * @return the id + */ + String getId(); + + /** + * Determines if the ticket is expired. Most common implementations might + * collaborate with ExpirationPolicy strategy. + * + * @return true, if the ticket is expired + * @see org.jasig.cas.ticket.ExpirationPolicy + */ + boolean isExpired(); + + /** + * Method to retrieve the TicketGrantingTicket that granted this ticket. + * + * @return the ticket or null if it has no parent + */ + TicketGrantingTicket getGrantingTicket(); + + /** + * Method to return the time the Ticket was created. + * + * @return the time the ticket was created. + */ + long getCreationTime(); + + /** + * @return the number of times this ticket was used. + */ + int getCountOfUses(); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/ticket/TicketException.java b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/TicketException.java new file mode 100644 index 000000000000..51ab42ba64e2 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/TicketException.java @@ -0,0 +1,62 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket; + +import org.jasig.cas.authentication.RootCasException; + +/** + * Generic ticket exception. Top of the TicketException hierarchy. + * + * @author Scott Battaglia + * @since 3.0.0 + * @deprecated As of 4.1, the class is required to note its abstractness in the name and will be renamed in the future. + */ +@Deprecated +public abstract class TicketException extends RootCasException { + private static final long serialVersionUID = -5128676415951733624L; + + /** + * Instantiates a new ticket exception. + * + * @param code the code + * @param throwable the throwable + */ + public TicketException(final String code, final Throwable throwable) { + super(code, throwable); + } + + /** + * Instantiates a new ticket exception. + * + * @param code the code + */ + public TicketException(final String code) { + super(code); + } + + /** + * Instantiates a new ticket exception. + * + * @param code the code + * @param msg the msg + */ + public TicketException(final String code, final String msg) { + super(code, msg); + } +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/ticket/TicketGrantingTicket.java b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/TicketGrantingTicket.java new file mode 100644 index 000000000000..c593982cf652 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/TicketGrantingTicket.java @@ -0,0 +1,125 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; + +import java.util.List; +import java.util.Map; + +/** + * Interface for a ticket granting ticket. A TicketGrantingTicket is the main + * access into the CAS service layer. Without a TicketGrantingTicket, a user of + * CAS cannot do anything. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public interface TicketGrantingTicket extends Ticket { + + /** The prefix to use when generating an id for a Ticket Granting Ticket. */ + String PREFIX = "TGT"; + + /** The prefix to use when generating an id for a Proxy Granting Ticket. */ + String PROXY_GRANTING_TICKET_PREFIX = "PGT"; + + /** The prefix to use when generating an id for a Proxy Granting Ticket IOU. */ + String PROXY_GRANTING_TICKET_IOU_PREFIX = "PGTIOU"; + + /** + * Method to retrieve the authentication. + * + * @return the authentication + */ + Authentication getAuthentication(); + + /** + * Gets a list of supplemental authentications associated with this ticket. + * A supplemental authentication is one other than the one used to create the ticket, + * for example, a forced authentication that happens after the beginning of a CAS SSO session. + * + * @return Non-null list of supplemental authentications. + */ + List getSupplementalAuthentications(); + + /** + * Grant a ServiceTicket for a specific service. + * + * @param id The unique identifier for this ticket. + * @param service The service for which we are granting a ticket + * @param expirationPolicy the expiration policy. + * @param credentialsProvided if the credentials are provided. + * @return the service ticket granted to a specific service for the + * principal of the TicketGrantingTicket + */ + ServiceTicket grantServiceTicket(String id, Service service, + ExpirationPolicy expirationPolicy, boolean credentialsProvided); + + /** + * Gets an immutable map of service ticket and services accessed by this ticket-granting ticket. + * + * @return an immutable map of service ticket and services accessed by this ticket-granting ticket. + */ + Map getServices(); + + /** + * Remove all services of the TGT (at logout). + */ + void removeAllServices(); + + /** + * Mark a ticket as expired. + */ + void markTicketExpired(); + + /** + * Convenience method to determine if the TicketGrantingTicket is the root + * of the hierarchy of tickets. + * + * @return true if it has no parent, false otherwise. + */ + boolean isRoot(); + + /** + * Gets the ticket-granting ticket at the root of the ticket hierarchy. + * + * @return Non-null root ticket-granting ticket. + */ + TicketGrantingTicket getRoot(); + + /** + * Gets all authentications ({@link #getAuthentication()}, {@link #getSupplementalAuthentications()}) from this + * instance and all dependent tickets that reference this one. + * + * @return Non-null list of authentication associated with this ticket in leaf-first order. + */ + List getChainedAuthentications(); + + + /** + * Gets the service that produced a proxy-granting ticket. + * + * @return Service that produced proxy-granting ticket or null if this is + * not a proxy-granting ticket. + * @since 4.1 + */ + Service getProxiedBy(); + +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/ticket/TicketState.java b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/TicketState.java new file mode 100644 index 000000000000..f0192529506e --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/TicketState.java @@ -0,0 +1,65 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket; + +import org.jasig.cas.authentication.Authentication; + +/** + * @author Scott Battaglia + + * @since 3.0.0.5 + */ +public interface TicketState { + + /** + * Returns the number of times a ticket was used. + * + * @return the number of times the ticket was used. + */ + int getCountOfUses(); + + /** + * Returns the last time the ticket was used. + * + * @return the last time the ticket was used. + */ + long getLastTimeUsed(); + + /** + * Get the second to last time used. + * + * @return the previous time used. + */ + + long getPreviousTimeUsed(); + + /** + * Get the time the ticket was created. + * + * @return the creation time of the ticket. + */ + long getCreationTime(); + + /** + * Authentication information from the ticket. This may be null. + * + * @return the authentication information. + */ + Authentication getAuthentication(); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/ticket/proxy/ProxyHandler.java b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/proxy/ProxyHandler.java new file mode 100644 index 000000000000..840043108c25 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/proxy/ProxyHandler.java @@ -0,0 +1,52 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.proxy; + +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.ticket.TicketGrantingTicket; + +/** + * Abstraction for what needs to be done to handle proxies. Useful because the + * generic flow for all authentication is similar the actions taken for proxying + * are different. One can swap in/out implementations but keep the flow of + * events the same. + * + * @author Scott Battaglia + * @author Misagh Moayyed + * @since 3.0.0 + */ +public interface ProxyHandler { + + /** + * Method to actually process the proxy request. + * + * @param credential The credential of the item that will be proxying. + * @param proxyGrantingTicketId The ticketId for the PGT (which really is a TGT) + * @return the String value that needs to be passed to the CAS client. + */ + String handle(Credential credential, TicketGrantingTicket proxyGrantingTicketId); + + /** + * Whether this handler can support the proxy request identified by the given credentials. + * + * @param credential the credential object containing the proxy request details. + * @return true, if successful + */ + boolean canHandle(Credential credential); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/ticket/registry/RegistryCleaner.java b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/registry/RegistryCleaner.java new file mode 100644 index 000000000000..d9c9f57117bd --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/registry/RegistryCleaner.java @@ -0,0 +1,38 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import org.jasig.cas.ticket.Ticket; + +import java.util.Collection; + +/** + * Strategy interface to denote the start of cleaning the registry. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public interface RegistryCleaner { + + /** + * Method to kick-off the cleaning of a registry. + * @return the collection of removed/cleaned tickets + */ + Collection clean(); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/ticket/registry/TicketRegistry.java b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/registry/TicketRegistry.java new file mode 100644 index 000000000000..990a7ad38fa3 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/registry/TicketRegistry.java @@ -0,0 +1,83 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import java.util.Collection; + +import org.jasig.cas.ticket.Ticket; + +/** + * Interface for a registry that stores tickets. The underlying registry can be + * backed by anything from a normal HashMap to JGroups for having distributed + * registries. It is up to specific implementations to determine their clean up + * strategy. Strategies can include a manual clean up by a RegistryCleaner or a + * more sophisticated strategy such as LRU. + * + * @author Scott Battaglia + + * @since 3.0.0 + *

+ * This is a published and supported CAS Server 3 API. + *

+ */ +public interface TicketRegistry { + + /** + * Add a ticket to the registry. Ticket storage is based on the ticket id. + * + * @param ticket The ticket we wish to add to the cache. + */ + void addTicket(Ticket ticket); + + /** + * Retrieve a ticket from the registry. If the ticket retrieved does not + * match the expected class, an InvalidTicketException is thrown. + * + * @param ticketId the id of the ticket we wish to retrieve. + * @param clazz The expected class of the ticket we wish to retrieve. + * @param the generic ticket type to return that extends {@link Ticket} + * @return the requested ticket. + */ + T getTicket(String ticketId, Class clazz); + + /** + * Retrieve a ticket from the registry. + * + * @param ticketId the id of the ticket we wish to retrieve + * @return the requested ticket. + */ + Ticket getTicket(String ticketId); + + /** + * Remove a specific ticket from the registry. + * + * @param ticketId The id of the ticket to delete. + * @return true if the ticket was removed and false if the ticket did not + * exist. + */ + boolean deleteTicket(String ticketId); + + /** + * Retrieve all tickets from the registry. + * + * @return collection of tickets currently stored in the registry. Tickets + * might or might not be valid i.e. expired. + */ + Collection getTickets(); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/ticket/registry/support/LockingStrategy.java b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/registry/support/LockingStrategy.java new file mode 100644 index 000000000000..db095775900b --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/ticket/registry/support/LockingStrategy.java @@ -0,0 +1,43 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry.support; + +/** + * Strategy pattern for defining a locking strategy in support of exclusive + * execution of some process. + * + * @author Marvin S. Addison + * @since 3.3.6 + * + */ +public interface LockingStrategy { + + /** + * Attempt to acquire the lock. + * + * @return True if lock was successfully acquired, false otherwise. + */ + boolean acquire(); + + + /** + * Release the lock if held. If the lock is not held nothing is done. + */ + void release(); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/util/services/RegisteredServiceCipherExecutor.java b/cas-server-core-api/src/main/java/org/jasig/cas/util/services/RegisteredServiceCipherExecutor.java new file mode 100644 index 000000000000..388a0e6b188b --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/util/services/RegisteredServiceCipherExecutor.java @@ -0,0 +1,39 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.util.services; + +import org.jasig.cas.services.RegisteredService; + +/** + * Defines how to encrypt data based on registered service's public key, etc. + * + * @author Misagh Moayyed + * @since 4.1 + */ +public interface RegisteredServiceCipherExecutor { + /** + * Encode string. + * + * @param data the data + * @param service the service + * @return the encoded string or null + */ + String encode(String data, RegisteredService service); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/validation/Assertion.java b/cas-server-core-api/src/main/java/org/jasig/cas/validation/Assertion.java new file mode 100644 index 000000000000..b7fc1704d570 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/validation/Assertion.java @@ -0,0 +1,68 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.validation; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; + +import java.util.List; + +/** + * Represents a security assertion obtained from a successfully validated ticket. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @see org.jasig.cas.CentralAuthenticationService#validateServiceTicket(String, org.jasig.cas.authentication.principal.Service) + * @since 3.0.0 + */ +public interface Assertion { + + /** + * Gets the authentication event that is basis of this assertion. + * + * @return Non-null primary authentication event. + */ + Authentication getPrimaryAuthentication(); + + /** + * Gets a list of all authentications that have occurred during a CAS SSO session. + * + * @return Non-null, non-empty list of authentications in leaf-first order (i.e. authentications on the root ticket + * occur at the end). + */ + List getChainedAuthentications(); + + /** + * True if the validated ticket was granted in the same transaction as that + * in which its grantor GrantingTicket was originally issued. + * + * @return true if validated ticket was granted simultaneous with its + * grantor's issuance + */ + boolean isFromNewLogin(); + + /** + * Method to obtain the service for which we are asserting this ticket is + * valid for. + * + * @return the service for which we are asserting this ticket is valid for. + */ + Service getService(); + +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/validation/ValidationSpecification.java b/cas-server-core-api/src/main/java/org/jasig/cas/validation/ValidationSpecification.java new file mode 100644 index 000000000000..8a2c394ab43e --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/validation/ValidationSpecification.java @@ -0,0 +1,36 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.validation; + +/** + * An interface to impose restrictions and requirements on validations (e.g. + * renew=true). + * + * @author William G. Thompson, Jr. + * @since 3.0.0 + */ +public interface ValidationSpecification { + + /** + * @param assertion The assertion we want to confirm is satisfied by this + * spec. + * @return true if it is, false otherwise. + */ + boolean isSatisfiedBy(Assertion assertion); +} diff --git a/cas-server-core-api/src/main/java/org/jasig/cas/web/support/ArgumentExtractor.java b/cas-server-core-api/src/main/java/org/jasig/cas/web/support/ArgumentExtractor.java new file mode 100644 index 000000000000..9df7bc6a02f6 --- /dev/null +++ b/cas-server-core-api/src/main/java/org/jasig/cas/web/support/ArgumentExtractor.java @@ -0,0 +1,39 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.support; + +import javax.servlet.http.HttpServletRequest; + +import org.jasig.cas.authentication.principal.WebApplicationService; + +/** + * Strategy interface for retrieving services from the request. + * + * @author Scott Battatglia + * @since 3.1 + */ +public interface ArgumentExtractor { + /** + * Retrieve the service from the request. + * + * @param request the request context. + * @return the fully formed Service or null if it could not be found. + */ + WebApplicationService extractService(HttpServletRequest request); +} diff --git a/cas-server-core/NOTICE b/cas-server-core/NOTICE new file mode 100644 index 000000000000..c20e9960e2c6 --- /dev/null +++ b/cas-server-core/NOTICE @@ -0,0 +1,108 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + c3p0:JDBC DataSources/Resource Pools under GNU LESSER GENERAL PUBLIC LICENSE + CDI APIs under Apache License, Version 2.0 + ClassMate under The Apache Software License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Hibernate JPA Support under GNU Lesser General Public License + Hibernate Validator Engine under Apache License, Version 2.0 + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Inspektr - Error Logging under Apache 2.0 License + Inspektr - Spring Framework Support under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + JBoss Logging I18n Annotations under Public Domain + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + quartz under The Apache Software License, Version 2.0 + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-core/pom.xml b/cas-server-core/pom.xml new file mode 100644 index 000000000000..1dbc5b650255 --- /dev/null +++ b/cas-server-core/pom.xml @@ -0,0 +1,237 @@ + + + + + + cas-server + org.jasig.cas + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-core + Apereo CAS Core + CAS core + + + org.jasig.cas + cas-server-core-api + ${project.version} + + + + org.jasig.inspektr + inspektr-audit + compile + + + + io.dropwizard.metrics + metrics-core + + + + io.dropwizard.metrics + metrics-annotation + + + + org.jasig.service.persondir + person-directory-impl + compile + + + + commons-codec + commons-codec + compile + + + + org.apache.commons + commons-lang3 + + + + org.springframework + spring-orm + compile + + + + org.springframework + spring-jdbc + compile + + + + org.springframework + spring-core + compile + + + + org.springframework + spring-beans + compile + + + + org.springframework + spring-webmvc + compile + + + + org.springframework + spring-context-support + compile + + + + org.springframework + spring-tx + + + + org.springframework + spring-context + + + + org.springframework.security + spring-security-core + compile + + + + org.hibernate + hibernate-validator + test + + + + org.hibernate + hibernate-core + compile + + + + org.hibernate + hibernate-entitymanager + test + + + + javassist + javassist + test + 3.12.1.GA + + + + org.springframework.webflow + spring-webflow + compile + + + + commons-jexl + commons-jexl + runtime + jar + + + + commons-io + commons-io + ${commons.io.version} + compile + jar + + + + joda-time + joda-time + compile + + + + org.reflections + reflections + compile + + + + org.quartz-scheduler + quartz + test + + + + org.jasig.inspektr + inspektr-support-spring + test + + + org.slf4j + slf4j-api + + + + + + org.apache.httpcomponents + httpclient + + + + javax.cache + cache-api + + + + org.jsr107.ri + cache-ri-impl + + + + com.fasterxml.jackson.core + jackson-databind + + + + com.google.guava + guava + + + + org.bitbucket.b_c + jose4j + + + org.apache.commons + commons-collections4 + + + + + ${project.parent.basedir} + + diff --git a/cas-server-core/src/main/java/org/jasig/cas/CasEnvironmentContextListener.java b/cas-server-core/src/main/java/org/jasig/cas/CasEnvironmentContextListener.java new file mode 100644 index 000000000000..4ccde47f20f5 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/CasEnvironmentContextListener.java @@ -0,0 +1,106 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import java.util.Formatter; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A context listener that reports back the CAS application + * deployment environment info. Details such as CAS versin, + * Java/OS info as well as the server container info are logged. + * @author Misagh Moayyed + * @since 4.1 + */ +@Component +public final class CasEnvironmentContextListener implements ServletContextListener { + private static final Logger LOGGER = LoggerFactory.getLogger(CasEnvironmentContextListener.class); + + private static AtomicBoolean INITIALIZED = new AtomicBoolean(false); + + /** + * Instantiates a new Cas environment context listener. + */ + public CasEnvironmentContextListener() { + super(); + LOGGER.debug("[{}] initialized...", CasEnvironmentContextListener.class.getSimpleName()); + } + + /** + * Logs environment info by collecting + * details on the java and os deployment + * environment. Data is logged at DEBUG + * level. + */ + @PostConstruct + public void logEnvironmentInfo() { + if (!INITIALIZED.get()) { + LOGGER.info(collectEnvironmentInfo()); + INITIALIZED.set(true); + } + } + + /** + * Collect environment info with + * details on the java and os deployment + * versions. + * + * @return environment info + */ + private String collectEnvironmentInfo() { + final Properties properties = System.getProperties(); + final Formatter formatter = new Formatter(); + formatter.format("\n******************** Welcome to CAS ********************\n"); + formatter.format("CAS Version: %s\n", CasVersion.getVersion()); + formatter.format("Java Home: %s\n", properties.get("java.home")); + formatter.format("Java Vendor: %s\n", properties.get("java.vendor")); + formatter.format("Java Version: %s\n", properties.get("java.version")); + formatter.format("OS Architecture: %s\n", properties.get("os.arch")); + formatter.format("OS Name: %s\n", properties.get("os.name")); + formatter.format("OS Version: %s\n", properties.get("os.version")); + formatter.format("*******************************************************\n"); + return formatter.toString(); + } + + @Override + public String toString() { + final ToStringBuilder builder = new ToStringBuilder(this); + builder.append(collectEnvironmentInfo()); + return builder.toString(); + } + + @Override + public void contextInitialized(final ServletContextEvent event) { + LOGGER.info("[{}] has loaded the CAS application context", + event.getServletContext().getServerInfo()); + } + + @Override + public void contextDestroyed(final ServletContextEvent event) {} +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/CasVersion.java b/cas-server-core/src/main/java/org/jasig/cas/CasVersion.java new file mode 100644 index 000000000000..f621132152e8 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/CasVersion.java @@ -0,0 +1,46 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas; + +/** + * Class that exposes the CAS version. Fetches the "Implementation-Version" + * manifest attribute from the jar file. + * + * @author Dmitriy Kopylenko + + * @since 3.0.0 + */ +public final class CasVersion { + + /** + * Private constructor for CasVersion. You should not be able to instantiate + * this class. + */ + private CasVersion() { + // this class is not instantiable + } + + /** + * @return Return the full CAS version string. + * @see java.lang.Package#getImplementationVersion + */ + public static String getVersion() { + return CasVersion.class.getPackage().getImplementationVersion(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/CentralAuthenticationServiceImpl.java b/cas-server-core/src/main/java/org/jasig/cas/CentralAuthenticationServiceImpl.java new file mode 100644 index 000000000000..806bdaf48ed3 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/CentralAuthenticationServiceImpl.java @@ -0,0 +1,638 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas; + +import com.codahale.metrics.annotation.Counted; +import com.codahale.metrics.annotation.Metered; +import com.codahale.metrics.annotation.Timed; +import org.jasig.cas.authentication.AuthenticationBuilder; +import org.jasig.cas.logout.LogoutRequest; +import org.jasig.inspektr.audit.annotation.Audit; +import org.apache.commons.collections4.Predicate; +import org.jasig.cas.authentication.AcceptAnyAuthenticationPolicyFactory; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.DefaultAuthenticationBuilder; +import org.jasig.cas.authentication.AuthenticationException; +import org.jasig.cas.authentication.AuthenticationManager; +import org.jasig.cas.authentication.ContextualAuthenticationPolicy; +import org.jasig.cas.authentication.ContextualAuthenticationPolicyFactory; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.MixedPrincipalException; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.jasig.cas.authentication.principal.PersistentIdGenerator; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.PrincipalFactory; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.logout.LogoutManager; +import org.jasig.cas.services.AttributeReleasePolicy; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServiceContext; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.services.UnauthorizedProxyingException; +import org.jasig.cas.services.UnauthorizedServiceException; +import org.jasig.cas.services.UnauthorizedServiceForPrincipalException; +import org.jasig.cas.services.UnauthorizedSsoServiceException; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.InvalidTicketException; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketCreationException; +import org.jasig.cas.ticket.TicketException; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.jasig.cas.ticket.UnrecognizableServiceForServiceTicketValidationException; +import org.jasig.cas.ticket.UnsatisfiedAuthenticationPolicyException; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.jasig.cas.validation.Assertion; +import org.jasig.cas.validation.ImmutableAssertion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.Assert; + +import javax.validation.constraints.NotNull; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Concrete implementation of a CentralAuthenticationService, and also the + * central, organizing component of CAS's internal implementation. + *

+ * This class is threadsafe. + *

+ * This class has the following properties that must be set: + *
    + *
  • ticketRegistry - The Ticket Registry to maintain the list + * of available tickets.
  • + *
  • serviceTicketRegistry - Provides an alternative to configure separate registries for + * TGTs and ST in order to store them in different locations (i.e. long term memory or short-term)
  • + *
  • authenticationManager - The service that will handle + * authentication.
  • + *
  • ticketGrantingTicketUniqueTicketIdGenerator - Plug in to + * generate unique secure ids for TicketGrantingTickets.
  • + *
  • serviceTicketUniqueTicketIdGenerator - Plug in to + * generate unique secure ids for ServiceTickets.
  • + *
  • ticketGrantingTicketExpirationPolicy - The expiration + * policy for TicketGrantingTickets.
  • + *
  • serviceTicketExpirationPolicy - The expiration policy for + * ServiceTickets.
  • + *
+ * + * @author William G. Thompson, Jr. + * @author Scott Battaglia + * @author Dmitry Kopylenko + * @since 3.0.0 + */ +public final class CentralAuthenticationServiceImpl implements CentralAuthenticationService { + + /** Log instance for logging events, info, warnings, errors, etc. */ + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** TicketRegistry for storing and retrieving tickets as needed. */ + @NotNull + private final TicketRegistry ticketRegistry; + + /** New Ticket Registry for storing and retrieving services tickets. Can point to the same one as the ticketRegistry variable. */ + @NotNull + private final TicketRegistry serviceTicketRegistry; + + /** + * AuthenticationManager for authenticating credentials for purposes of + * obtaining tickets. + */ + @NotNull + private final AuthenticationManager authenticationManager; + + /** + * UniqueTicketIdGenerator to generate ids for TicketGrantingTickets + * created. + */ + @NotNull + private final UniqueTicketIdGenerator ticketGrantingTicketUniqueTicketIdGenerator; + + /** Map to contain the mappings of service->UniqueTicketIdGenerators. */ + @NotNull + private final Map uniqueTicketIdGeneratorsForService; + + /** Implementation of Service Manager. */ + @NotNull + private final ServicesManager servicesManager; + + /** The logout manager. **/ + @NotNull + private final LogoutManager logoutManager; + + /** Expiration policy for ticket granting tickets. */ + @NotNull + private ExpirationPolicy ticketGrantingTicketExpirationPolicy; + + /** ExpirationPolicy for Service Tickets. */ + @NotNull + private ExpirationPolicy serviceTicketExpirationPolicy; + + /** + * Authentication policy that uses a service context to produce stateful security policies to apply when + * authenticating credentials. + */ + @NotNull + private ContextualAuthenticationPolicyFactory serviceContextAuthenticationPolicyFactory = + new AcceptAnyAuthenticationPolicyFactory(); + + /** Factory to create the principal type. **/ + @NotNull + private PrincipalFactory principalFactory = new DefaultPrincipalFactory(); + + /** Default instance for the ticket id generator. */ + @NotNull + private final UniqueTicketIdGenerator defaultServiceTicketIdGenerator + = new DefaultUniqueTicketIdGenerator(); + /** + * Build the central authentication service implementation. + * + * @param ticketRegistry the tickets registry. + * @param serviceTicketRegistry the service tickets registry. + * @param authenticationManager the authentication manager. + * @param ticketGrantingTicketUniqueTicketIdGenerator the TGT id generator. + * @param uniqueTicketIdGeneratorsForService the map with service and ticket id generators. + * @param ticketGrantingTicketExpirationPolicy the TGT expiration policy. + * @param serviceTicketExpirationPolicy the service ticket expiration policy. + * @param servicesManager the services manager. + * @param logoutManager the logout manager. + */ + public CentralAuthenticationServiceImpl(final TicketRegistry ticketRegistry, + final TicketRegistry serviceTicketRegistry, + final AuthenticationManager authenticationManager, + final UniqueTicketIdGenerator ticketGrantingTicketUniqueTicketIdGenerator, + final Map uniqueTicketIdGeneratorsForService, + final ExpirationPolicy ticketGrantingTicketExpirationPolicy, + final ExpirationPolicy serviceTicketExpirationPolicy, + final ServicesManager servicesManager, + final LogoutManager logoutManager) { + this.ticketRegistry = ticketRegistry; + if (serviceTicketRegistry == null) { + this.serviceTicketRegistry = ticketRegistry; + } else { + this.serviceTicketRegistry = serviceTicketRegistry; + } + this.authenticationManager = authenticationManager; + this.ticketGrantingTicketUniqueTicketIdGenerator = ticketGrantingTicketUniqueTicketIdGenerator; + this.uniqueTicketIdGeneratorsForService = uniqueTicketIdGeneratorsForService; + this.ticketGrantingTicketExpirationPolicy = ticketGrantingTicketExpirationPolicy; + this.serviceTicketExpirationPolicy = serviceTicketExpirationPolicy; + this.servicesManager = servicesManager; + this.logoutManager = logoutManager; + } + + /** + * {@inheritDoc} + * Destroy a TicketGrantingTicket and perform back channel logout. This has the effect of invalidating any + * Ticket that was derived from the TicketGrantingTicket being destroyed. May throw an + * {@link IllegalArgumentException} if the TicketGrantingTicket ID is null. + * + * @param ticketGrantingTicketId the id of the ticket we want to destroy + * @return the logout requests. + */ + @Audit( + action="TICKET_GRANTING_TICKET_DESTROYED", + actionResolverName="DESTROY_TICKET_GRANTING_TICKET_RESOLVER", + resourceResolverName="DESTROY_TICKET_GRANTING_TICKET_RESOURCE_RESOLVER") + @Timed(name = "DESTROY_TICKET_GRANTING_TICKET_TIMER") + @Metered(name="DESTROY_TICKET_GRANTING_TICKET_METER") + @Counted(name="DESTROY_TICKET_GRANTING_TICKET_COUNTER", monotonic=true) + @Override + public List destroyTicketGrantingTicket(@NotNull final String ticketGrantingTicketId) { + try { + logger.debug("Removing ticket [{}] from registry...", ticketGrantingTicketId); + final TicketGrantingTicket ticket = getTicket(ticketGrantingTicketId, TicketGrantingTicket.class); + logger.debug("Ticket found. Processing logout requests and then deleting the ticket..."); + final List logoutRequests = logoutManager.performLogout(ticket); + this.ticketRegistry.deleteTicket(ticketGrantingTicketId); + return logoutRequests; + } catch (final InvalidTicketException e) { + logger.debug("TicketGrantingTicket [{}] cannot be found in the ticket registry.", ticketGrantingTicketId); + } + return Collections.emptyList(); + } + + @Audit( + action="SERVICE_TICKET", + actionResolverName="GRANT_SERVICE_TICKET_RESOLVER", + resourceResolverName="GRANT_SERVICE_TICKET_RESOURCE_RESOLVER") + @Timed(name="GRANT_SERVICE_TICKET_TIMER") + @Metered(name="GRANT_SERVICE_TICKET_METER") + @Counted(name="GRANT_SERVICE_TICKET_COUNTER", monotonic=true) + @Override + public ServiceTicket grantServiceTicket( + final String ticketGrantingTicketId, + final Service service, final Credential... credentials) + throws AuthenticationException, TicketException { + + final TicketGrantingTicket ticketGrantingTicket = getTicket(ticketGrantingTicketId, TicketGrantingTicket.class); + final RegisteredService registeredService = this.servicesManager.findServiceBy(service); + + verifyRegisteredServiceProperties(registeredService, service); + final Set sanitizedCredentials = sanitizeCredentials(credentials); + + Authentication currentAuthentication = null; + if (sanitizedCredentials.size() > 0) { + currentAuthentication = this.authenticationManager.authenticate( + sanitizedCredentials.toArray(new Credential[] {})); + final Authentication original = ticketGrantingTicket.getAuthentication(); + if (!currentAuthentication.getPrincipal().equals(original.getPrincipal())) { + throw new MixedPrincipalException( + currentAuthentication, currentAuthentication.getPrincipal(), original.getPrincipal()); + } + ticketGrantingTicket.getSupplementalAuthentications().add(currentAuthentication); + } + + if (currentAuthentication == null && !registeredService.getAccessStrategy().isServiceAccessAllowedForSso()) { + logger.warn("ServiceManagement: Service [{}] is not allowed to use SSO.", service.getId()); + throw new UnauthorizedSsoServiceException(); + } + + final Service proxiedBy = ticketGrantingTicket.getProxiedBy(); + if (proxiedBy != null) { + logger.debug("TGT is proxied by [{}]. Locating proxy service in registry...", proxiedBy.getId()); + final RegisteredService proxyingService = servicesManager.findServiceBy(proxiedBy); + + if (proxyingService != null) { + logger.debug("Located proxying service [{}] in the service registry", proxyingService); + if (!proxyingService.getProxyPolicy().isAllowedToProxy()) { + logger.warn("Found proxying service {}, but it is not authorized to fulfill the proxy attempt made by {}", + proxyingService.getId(), service.getId()); + throw new UnauthorizedProxyingException("Proxying is not allowed for registered service " + + registeredService.getId()); + } + } else { + logger.warn("No proxying service found. Proxy attempt by service [{}] (registered service [{}]) is not allowed.", + service.getId(), registeredService.getId()); + throw new UnauthorizedProxyingException("Proxying is not allowed for registered service " + + registeredService.getId()); + } + } else { + logger.trace("TGT is not proxied by another service"); + } + + // Perform security policy check by getting the authentication that satisfies the configured policy + // This throws if no suitable policy is found + getAuthenticationSatisfiedByPolicy(ticketGrantingTicket, new ServiceContext(service, registeredService)); + + final List authentications = ticketGrantingTicket.getChainedAuthentications(); + final Principal principal = authentications.get(authentications.size() - 1).getPrincipal(); + + final Map principalAttrs = registeredService.getAttributeReleasePolicy().getAttributes(principal); + if (!registeredService.getAccessStrategy().doPrincipalAttributesAllowServiceAccess(principalAttrs)) { + logger.warn("ServiceManagement: Cannot grant service ticket because Service [{}] is not authorized for use by [{}].", + service.getId(), principal); + throw new UnauthorizedServiceForPrincipalException(); + } + + final String uniqueTicketIdGenKey = service.getClass().getName(); + logger.debug("Looking up service ticket id generator for [{}]", uniqueTicketIdGenKey); + UniqueTicketIdGenerator serviceTicketUniqueTicketIdGenerator = + this.uniqueTicketIdGeneratorsForService.get(uniqueTicketIdGenKey); + if (serviceTicketUniqueTicketIdGenerator == null) { + serviceTicketUniqueTicketIdGenerator = this.defaultServiceTicketIdGenerator; + logger.debug("Service ticket id generator not found for [{}]. Using the default generator...", + uniqueTicketIdGenKey); + } + + final String ticketPrefix = authentications.size() == 1 ? ServiceTicket.PREFIX : ServiceTicket.PROXY_TICKET_PREFIX; + final String ticketId = serviceTicketUniqueTicketIdGenerator.getNewTicketId(ticketPrefix); + final ServiceTicket serviceTicket = ticketGrantingTicket.grantServiceTicket( + ticketId, + service, + this.serviceTicketExpirationPolicy, + currentAuthentication != null); + + this.serviceTicketRegistry.addTicket(serviceTicket); + + logger.info("Granted ticket [{}] for service [{}] for user [{}]", + serviceTicket.getId(), service.getId(), principal.getId()); + + return serviceTicket; + } + + /** + * Attempts to sanitize the array of credentials by removing + * all null elements from the collection. + * @param credentials credentials to sanitize + * @return a set of credentials with no null values + */ + private static Set sanitizeCredentials(final Credential[] credentials) { + if (credentials != null && credentials.length > 0) { + final Set set = new HashSet<>(Arrays.asList(credentials)); + final Iterator it = set.iterator(); + while (it.hasNext()) { + if (it.next() == null) { + it.remove(); + } + } + return set; + } + return Collections.emptySet(); + } + + @Audit( + action="SERVICE_TICKET", + actionResolverName="GRANT_SERVICE_TICKET_RESOLVER", + resourceResolverName="GRANT_SERVICE_TICKET_RESOURCE_RESOLVER") + @Timed(name = "GRANT_SERVICE_TICKET_TIMER") + @Metered(name="GRANT_SERVICE_TICKET_METER") + @Counted(name="GRANT_SERVICE_TICKET_COUNTER", monotonic=true) + @Override + public ServiceTicket grantServiceTicket(final String ticketGrantingTicketId, + final Service service) throws TicketException { + try { + return this.grantServiceTicket(ticketGrantingTicketId, service, (Credential[]) null); + } catch (final AuthenticationException e) { + throw new IllegalStateException("Unexpected authentication exception", e); + } + } + + @Audit( + action="PROXY_GRANTING_TICKET", + actionResolverName="GRANT_PROXY_GRANTING_TICKET_RESOLVER", + resourceResolverName="GRANT_PROXY_GRANTING_TICKET_RESOURCE_RESOLVER") + @Timed(name="GRANT_PROXY_GRANTING_TICKET_TIMER") + @Metered(name="GRANT_PROXY_GRANTING_TICKET_METER") + @Counted(name="GRANT_PROXY_GRANTING_TICKET_COUNTER", monotonic=true) + @Override + public TicketGrantingTicket delegateTicketGrantingTicket(final String serviceTicketId, final Credential... credentials) + throws AuthenticationException, TicketException { + + final ServiceTicket serviceTicket = this.serviceTicketRegistry.getTicket(serviceTicketId, ServiceTicket.class); + + if (serviceTicket == null || serviceTicket.isExpired()) { + logger.debug("ServiceTicket [{}] has expired or cannot be found in the ticket registry", serviceTicketId); + throw new InvalidTicketException(serviceTicketId); + } + + final RegisteredService registeredService = this.servicesManager + .findServiceBy(serviceTicket.getService()); + + verifyRegisteredServiceProperties(registeredService, serviceTicket.getService()); + + if (!registeredService.getProxyPolicy().isAllowedToProxy()) { + logger.warn("ServiceManagement: Service [{}] attempted to proxy, but is not allowed.", serviceTicket.getService().getId()); + throw new UnauthorizedProxyingException(); + } + + final Authentication authentication = this.authenticationManager.authenticate(credentials); + + final String pgtId = this.ticketGrantingTicketUniqueTicketIdGenerator.getNewTicketId( + TicketGrantingTicket.PROXY_GRANTING_TICKET_PREFIX); + final TicketGrantingTicket proxyGrantingTicket = serviceTicket.grantTicketGrantingTicket(pgtId, + authentication, this.ticketGrantingTicketExpirationPolicy); + + logger.debug("Generated proxy granting ticket [{}] based off of [{}]", proxyGrantingTicket, serviceTicketId); + this.ticketRegistry.addTicket(proxyGrantingTicket); + + return proxyGrantingTicket; + } + + @Audit( + action="SERVICE_TICKET_VALIDATE", + actionResolverName="VALIDATE_SERVICE_TICKET_RESOLVER", + resourceResolverName="VALIDATE_SERVICE_TICKET_RESOURCE_RESOLVER") + @Timed(name="VALIDATE_SERVICE_TICKET_TIMER") + @Metered(name="VALIDATE_SERVICE_TICKET_METER") + @Counted(name="VALIDATE_SERVICE_TICKET_COUNTER", monotonic=true) + @Override + public Assertion validateServiceTicket(final String serviceTicketId, final Service service) throws TicketException { + final RegisteredService registeredService = this.servicesManager.findServiceBy(service); + verifyRegisteredServiceProperties(registeredService, service); + + final ServiceTicket serviceTicket = this.serviceTicketRegistry.getTicket(serviceTicketId, ServiceTicket.class); + + if (serviceTicket == null) { + logger.info("Service ticket [{}] does not exist.", serviceTicketId); + throw new InvalidTicketException(serviceTicketId); + } + + try { + synchronized (serviceTicket) { + if (serviceTicket.isExpired()) { + logger.info("ServiceTicket [{}] has expired.", serviceTicketId); + throw new InvalidTicketException(serviceTicketId); + } + + if (!serviceTicket.isValidFor(service)) { + logger.error("Service ticket [{}] with service [{}] does not match supplied service [{}]", + serviceTicketId, serviceTicket.getService().getId(), service); + throw new UnrecognizableServiceForServiceTicketValidationException(serviceTicket.getService()); + } + } + + final TicketGrantingTicket root = serviceTicket.getGrantingTicket().getRoot(); + final Authentication authentication = getAuthenticationSatisfiedByPolicy( + root, new ServiceContext(serviceTicket.getService(), registeredService)); + final Principal principal = authentication.getPrincipal(); + + final AttributeReleasePolicy attributePolicy = registeredService.getAttributeReleasePolicy(); + logger.debug("Attribute policy [{}] is associated with service [{}]", attributePolicy, registeredService); + + @SuppressWarnings("unchecked") + final Map attributesToRelease = attributePolicy != null + ? attributePolicy.getAttributes(principal) : Collections.EMPTY_MAP; + + final String principalId = registeredService.getUsernameAttributeProvider().resolveUsername(principal, service); + final Principal modifiedPrincipal = this.principalFactory.createPrincipal(principalId, attributesToRelease); + final AuthenticationBuilder builder = DefaultAuthenticationBuilder.newInstance(authentication); + builder.setPrincipal(modifiedPrincipal); + + return new ImmutableAssertion( + builder.build(), + serviceTicket.getGrantingTicket().getChainedAuthentications(), + serviceTicket.getService(), + serviceTicket.isFromNewLogin()); + } finally { + if (serviceTicket.isExpired()) { + this.serviceTicketRegistry.deleteTicket(serviceTicketId); + } + } + } + + @Audit( + action="TICKET_GRANTING_TICKET", + actionResolverName="CREATE_TICKET_GRANTING_TICKET_RESOLVER", + resourceResolverName="CREATE_TICKET_GRANTING_TICKET_RESOURCE_RESOLVER") + @Timed(name = "CREATE_TICKET_GRANTING_TICKET_TIMER") + @Metered(name = "CREATE_TICKET_GRANTING_TICKET_METER") + @Counted(name="CREATE_TICKET_GRANTING_TICKET_COUNTER", monotonic=true) + @Override + public TicketGrantingTicket createTicketGrantingTicket(final Credential... credentials) + throws AuthenticationException, TicketException { + + final Set sanitizedCredentials = sanitizeCredentials(credentials); + if (sanitizedCredentials.size() > 0) { + final Authentication authentication = this.authenticationManager.authenticate(credentials); + + final TicketGrantingTicket ticketGrantingTicket = new TicketGrantingTicketImpl( + this.ticketGrantingTicketUniqueTicketIdGenerator + .getNewTicketId(TicketGrantingTicket.PREFIX), + authentication, this.ticketGrantingTicketExpirationPolicy); + + this.ticketRegistry.addTicket(ticketGrantingTicket); + return ticketGrantingTicket; + } + final String msg = "No credentials were specified in the request for creating a new ticket-granting ticket"; + logger.warn(msg); + throw new TicketCreationException(new IllegalArgumentException(msg)); + } + + /** + * {@inheritDoc} + */ + @Timed(name = "GET_TICKET_TIMER") + @Metered(name = "GET_TICKET_METER") + @Counted(name="GET_TICKET_COUNTER", monotonic=true) + @Override + public T getTicket(final String ticketId, final Class clazz) + throws InvalidTicketException { + Assert.notNull(ticketId, "ticketId cannot be null"); + final Ticket ticket = this.ticketRegistry.getTicket(ticketId, clazz); + + if (ticket == null) { + logger.debug("Ticket [{}] by type [{}] cannot be found in the ticket registry.", ticketId, clazz.getSimpleName()); + throw new InvalidTicketException(ticketId); + } + + if (ticket instanceof TicketGrantingTicket) { + synchronized (ticket) { + if (ticket.isExpired()) { + this.ticketRegistry.deleteTicket(ticketId); + logger.debug("Ticket [{}] has expired and is now deleted from the ticket registry.", ticketId); + throw new InvalidTicketException(ticketId); + } + } + } + return (T) ticket; + } + /** + * {@inheritDoc} + */ + @Timed(name = "GET_TICKETS_TIMER") + @Metered(name = "GET_TICKETS_METER") + @Counted(name="GET_TICKETS_COUNTER", monotonic=true) + @Override + public Collection getTickets(final Predicate predicate) { + final Collection c = new HashSet<>(this.ticketRegistry.getTickets()); + final Iterator it = c.iterator(); + while (it.hasNext()) { + if (!predicate.evaluate(it.next())) { + it.remove(); + } + } + return c; + } + + public void setServiceContextAuthenticationPolicyFactory(final ContextualAuthenticationPolicyFactory policy) { + this.serviceContextAuthenticationPolicyFactory = policy; + } + + /** + * @param ticketGrantingTicketExpirationPolicy a TGT expiration policy. + */ + public void setTicketGrantingTicketExpirationPolicy(final ExpirationPolicy ticketGrantingTicketExpirationPolicy) { + this.ticketGrantingTicketExpirationPolicy = ticketGrantingTicketExpirationPolicy; + } + + /** + * @param serviceTicketExpirationPolicy a ST expiration policy. + */ + public void setServiceTicketExpirationPolicy(final ExpirationPolicy serviceTicketExpirationPolicy) { + this.serviceTicketExpirationPolicy = serviceTicketExpirationPolicy; + } + + /** + * @deprecated + * Sets persistent id generator. + * + * @param persistentIdGenerator the persistent id generator + */ + @Deprecated + public void setPersistentIdGenerator(final PersistentIdGenerator persistentIdGenerator) { + logger.warn("setPersistentIdGenerator() is deprecated and no longer available. Consider " + + "configuring the an attribute provider for service definitions."); + } + + /** + * Sets principal factory to create principal objects. + * + * @param principalFactory the principal factory + */ + public void setPrincipalFactory(final PrincipalFactory principalFactory) { + this.principalFactory = principalFactory; + } + + + /** + * Gets the authentication satisfied by policy. + * + * @param ticket the ticket + * @param context the context + * @return the authentication satisfied by policy + * @throws org.jasig.cas.ticket.TicketException the ticket exception + */ + private Authentication getAuthenticationSatisfiedByPolicy( + final TicketGrantingTicket ticket, final ServiceContext context) throws TicketException { + + final ContextualAuthenticationPolicy policy = + serviceContextAuthenticationPolicyFactory.createPolicy(context); + if (policy.isSatisfiedBy(ticket.getAuthentication())) { + return ticket.getAuthentication(); + } + for (final Authentication auth : ticket.getSupplementalAuthentications()) { + if (policy.isSatisfiedBy(auth)) { + return auth; + } + } + throw new UnsatisfiedAuthenticationPolicyException(policy); + } + + /** + * Ensure that the service is found and enabled in the service registry. + * @param registeredService the located entry in the registry + * @param service authenticating service + * @throws UnauthorizedServiceException + */ + private void verifyRegisteredServiceProperties(final RegisteredService registeredService, final Service service) { + if (registeredService == null) { + final String msg = String.format("ServiceManagement: Unauthorized Service Access. " + + "Service [%s] is not found in service registry.", service.getId()); + logger.warn(msg); + throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE, msg); + } + if (!registeredService.getAccessStrategy().isServiceAccessAllowed()) { + final String msg = String.format("ServiceManagement: Unauthorized Service Access. " + + "Service [%s] is not enabled in service registry.", service.getId()); + + logger.warn(msg); + throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE, msg); + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/DefaultMessageDescriptor.java b/cas-server-core/src/main/java/org/jasig/cas/DefaultMessageDescriptor.java new file mode 100644 index 000000000000..e47f981328e5 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/DefaultMessageDescriptor.java @@ -0,0 +1,112 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas; + +import com.google.common.collect.ImmutableList; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.springframework.util.Assert; + +import java.io.Serializable; +import java.util.Arrays; + +/** + * Simple parameterized message descriptor with a code that refers to a message bundle key and a default + * message string to use if no message code can be resolved. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class DefaultMessageDescriptor implements MessageDescriptor { + + /** Serialization support. */ + private static final long serialVersionUID = 1227390629186486032L; + + private final String code; + + private final String defaultMessage; + + private final Serializable[] params; + + /** + * Instantiates a new message. + * + * @param code the code + */ + public DefaultMessageDescriptor(final String code) { + this(code, code); + } + + /** + * Instantiates a new message. + * + * @param code the code + * @param defaultMessage the default message + * @param params the params + */ + public DefaultMessageDescriptor(final String code, final String defaultMessage, final Serializable... params) { + Assert.hasText(code, "Code cannot be null or empty"); + Assert.hasText(defaultMessage, "Default message cannot be null or empty"); + this.code = code; + this.defaultMessage = defaultMessage; + this.params = params; + } + + public String getCode() { + return this.code; + } + + public String getDefaultMessage() { + return this.defaultMessage; + } + + /** + * Get parameters for the message. + * + * @return the serializable [ ] + */ + public Serializable[] getParams() { + if (this.params == null) { + return null; + } + return ImmutableList.copyOf(this.params).toArray(new Serializable[this.params.length]); + } + + @Override + public int hashCode() { + final HashCodeBuilder builder = new HashCodeBuilder(99, 31); + builder.append(this.code); + builder.append(this.defaultMessage); + builder.append(this.params); + return builder.toHashCode(); + } + + @Override + public boolean equals(final Object other) { + if (other == null || !(other instanceof DefaultMessageDescriptor)) { + return false; + } + if (other == this) { + return true; + } + final DefaultMessageDescriptor m = (DefaultMessageDescriptor) other; + return this.code.equals(m.getCode()) + && this.defaultMessage.equals(m.getDefaultMessage()) + && Arrays.equals(this.params, m.getParams()); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/audit/spi/CredentialsAsFirstParameterResourceResolver.java b/cas-server-core/src/main/java/org/jasig/cas/audit/spi/CredentialsAsFirstParameterResourceResolver.java new file mode 100644 index 000000000000..29f5c532bf87 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/audit/spi/CredentialsAsFirstParameterResourceResolver.java @@ -0,0 +1,58 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.audit.spi; + +import java.util.Arrays; + +import org.aspectj.lang.JoinPoint; + +import org.jasig.inspektr.audit.spi.AuditResourceResolver; + +import org.jasig.cas.util.AopUtils; + +/** + * Converts the Credential object into a String resource identifier. + * + * @author Scott Battaglia + * @since 3.1.2 + * + */ +public final class CredentialsAsFirstParameterResourceResolver implements AuditResourceResolver { + + @Override + public String[] resolveFrom(final JoinPoint joinPoint, final Object retval) { + return toResources(AopUtils.unWrapJoinPoint(joinPoint).getArgs()); + } + + @Override + public String[] resolveFrom(final JoinPoint joinPoint, final Exception exception) { + return toResources(AopUtils.unWrapJoinPoint(joinPoint).getArgs()); + } + + + /** + * Turn the arguments into a list. + * + * @param args the args + * @return the string[] + */ + private static String[] toResources(final Object[] args) { + return new String[] {"supplied credentials: " + Arrays.asList((Object[]) args[0])}; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/audit/spi/ServiceManagementResourceResolver.java b/cas-server-core/src/main/java/org/jasig/cas/audit/spi/ServiceManagementResourceResolver.java new file mode 100644 index 000000000000..54cecfc17489 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/audit/spi/ServiceManagementResourceResolver.java @@ -0,0 +1,64 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.audit.spi; + +import org.jasig.inspektr.audit.spi.AuditResourceResolver; + +import org.aspectj.lang.JoinPoint; +import org.jasig.cas.util.AopUtils; + + +/** + * Resolves a service id to the service. + *

+ * The expectation is that args[0] is a Long. + * + * @author Scott Battaglia + * @since 3.4.6 + */ +public final class ServiceManagementResourceResolver implements AuditResourceResolver { + + @Override + public String[] resolveFrom(final JoinPoint target, final Object returnValue) { + return findService(target); + } + + @Override + public String[] resolveFrom(final JoinPoint target, final Exception exception) { + return findService(target); + } + + /** + * Find service. + * + * @param joinPoint the join point + * @return the string[] + */ + private String[] findService(final JoinPoint joinPoint) { + final JoinPoint j = AopUtils.unWrapJoinPoint(joinPoint); + + final Long id = (Long) j.getArgs()[0]; + + if (id == null) { + return new String[] {""}; + } + + return new String[] {"id=" + id}; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/audit/spi/ServiceResourceResolver.java b/cas-server-core/src/main/java/org/jasig/cas/audit/spi/ServiceResourceResolver.java new file mode 100644 index 000000000000..b33406a6e7d2 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/audit/spi/ServiceResourceResolver.java @@ -0,0 +1,51 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.audit.spi; + +import org.aspectj.lang.JoinPoint; + +import org.jasig.inspektr.audit.spi.AuditResourceResolver; + +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.util.AopUtils; + +/** + * + * @author Scott Battaglia + * @since 3.1.2 + * + */ +public final class ServiceResourceResolver implements AuditResourceResolver { + + @Override + public String[] resolveFrom(final JoinPoint joinPoint, final Object retval) { + final Service service = (Service) AopUtils.unWrapJoinPoint(joinPoint).getArgs()[1]; + final StringBuilder builder = new StringBuilder(retval.toString()); + builder.append(" for "); + builder.append(service.getId()); + + return new String[] {builder.toString()}; + } + + @Override + public String[] resolveFrom(final JoinPoint joinPoint, final Exception ex) { + final Service service = (Service) AopUtils.unWrapJoinPoint(joinPoint).getArgs()[1]; + return new String[] {service.getId()}; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/audit/spi/TicketAsFirstParameterResourceResolver.java b/cas-server-core/src/main/java/org/jasig/cas/audit/spi/TicketAsFirstParameterResourceResolver.java new file mode 100644 index 000000000000..ad54878c4496 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/audit/spi/TicketAsFirstParameterResourceResolver.java @@ -0,0 +1,43 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.audit.spi; + +import org.jasig.inspektr.audit.spi.AuditResourceResolver; +import org.aspectj.lang.JoinPoint; +import org.jasig.cas.util.AopUtils; + +/** + * Implementation of the ResourceResolver that can determine the Ticket Id from the first parameter of the method call. + + * @author Scott Battaglia + * @since 3.1.2 + * + */ +public final class TicketAsFirstParameterResourceResolver implements AuditResourceResolver { + + @Override + public String[] resolveFrom(final JoinPoint joinPoint, final Exception exception) { + return new String[] {AopUtils.unWrapJoinPoint(joinPoint).getArgs()[0].toString()}; + } + + @Override + public String[] resolveFrom(final JoinPoint joinPoint, final Object object) { + return new String[] {AopUtils.unWrapJoinPoint(joinPoint).getArgs()[0].toString()}; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/audit/spi/TicketOrCredentialPrincipalResolver.java b/cas-server-core/src/main/java/org/jasig/cas/audit/spi/TicketOrCredentialPrincipalResolver.java new file mode 100644 index 000000000000..ef60d7ca1629 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/audit/spi/TicketOrCredentialPrincipalResolver.java @@ -0,0 +1,154 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.audit.spi; + +import org.jasig.inspektr.common.spi.PrincipalResolver; +import org.aspectj.lang.JoinPoint; +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.ticket.InvalidTicketException; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.jasig.cas.util.AopUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; + +import javax.validation.constraints.NotNull; + +/** + * PrincipalResolver that can retrieve the username from either the Ticket or from the Credential. + * + * @author Scott Battaglia + * @since 3.1.2 + * + */ +public final class TicketOrCredentialPrincipalResolver implements PrincipalResolver { + + /** Logger instance. */ + private static final Logger LOGGER = LoggerFactory.getLogger(TicketOrCredentialPrincipalResolver.class); + + @NotNull + private final CentralAuthenticationService centralAuthenticationService; + + /** + * Instantiates a new ticket or credential principal resolver. + * + * @deprecated As of 4.1 access to the registry is no longer relevant + * Consider using alternative constructors instead. + * @param ticketRegistry the ticket registry + */ + @Deprecated + public TicketOrCredentialPrincipalResolver(final TicketRegistry ticketRegistry) { + LOGGER.warn("The constructor is deprecated and will be removed. Consider an alternate constructor"); + this.centralAuthenticationService = null; + } + + /** + * Instantiates a new Ticket or credential principal resolver. + * + * @param centralAuthenticationService the central authentication service + * @since 4.1.0 + */ + public TicketOrCredentialPrincipalResolver(final CentralAuthenticationService centralAuthenticationService) { + this.centralAuthenticationService = centralAuthenticationService; + } + + @Override + public String resolveFrom(final JoinPoint joinPoint, final Object retVal) { + return resolveFromInternal(AopUtils.unWrapJoinPoint(joinPoint)); + } + + @Override + public String resolveFrom(final JoinPoint joinPoint, final Exception retVal) { + return resolveFromInternal(AopUtils.unWrapJoinPoint(joinPoint)); + } + + @Override + public String resolve() { + return UNKNOWN_USER; + } + + /** + * Resolve the principal from the join point given. + * + * @param joinPoint the join point + * @return the principal id, or {@link PrincipalResolver#UNKNOWN_USER} + */ + protected String resolveFromInternal(final JoinPoint joinPoint) { + final StringBuilder builder = new StringBuilder(); + + final Object arg1 = joinPoint.getArgs()[0]; + if (arg1.getClass().isArray()) { + final Object[] args1AsArray = (Object[]) arg1; + for (final Object arg: args1AsArray) { + builder.append(resolveArgument(arg)); + } + } else { + builder.append(resolveArgument(arg1)); + } + + return builder.toString(); + + } + + /** + * Resolve the join point argument. + * + * @param arg1 the arg + * @return the resolved string + */ + private String resolveArgument(final Object arg1) { + LOGGER.debug("Resolving argument [{}] for audit", arg1.getClass().getSimpleName()); + + if (arg1 instanceof Credential) { + return arg1.toString(); + } else if (arg1 instanceof String) { + try { + final Ticket ticket = this.centralAuthenticationService.getTicket((String) arg1, Ticket.class); + if (ticket instanceof ServiceTicket) { + final ServiceTicket serviceTicket = (ServiceTicket) ticket; + return serviceTicket.getGrantingTicket().getAuthentication().getPrincipal().getId(); + } else if (ticket instanceof TicketGrantingTicket) { + final TicketGrantingTicket tgt = (TicketGrantingTicket) ticket; + return tgt.getAuthentication().getPrincipal().getId(); + } + } catch (final InvalidTicketException e) { + LOGGER.trace(e.getMessage(), e); + } + LOGGER.debug("Could not locate ticket [{}] in the registry", arg1); + } else { + final SecurityContext securityContext = SecurityContextHolder.getContext(); + if (securityContext != null) { + final Authentication authentication = securityContext.getAuthentication(); + + if (authentication != null) { + return ((UserDetails) authentication.getPrincipal()).getUsername(); + } + } + } + LOGGER.debug("Unable to determine the audit argument. Returning [{}]", UNKNOWN_USER); + return UNKNOWN_USER; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/AbstractAuthenticationHandler.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/AbstractAuthenticationHandler.java new file mode 100644 index 000000000000..9adb19b24292 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/AbstractAuthenticationHandler.java @@ -0,0 +1,66 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.jasig.cas.authentication.principal.PrincipalFactory; + +import javax.validation.constraints.NotNull; + +/** + * Base class for all authentication handlers that support configurable naming. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public abstract class AbstractAuthenticationHandler implements AuthenticationHandler { + + /** Factory to create the principal type. **/ + @NotNull + protected PrincipalFactory principalFactory = new DefaultPrincipalFactory(); + + /** Configurable handler name. */ + private String name; + + @Override + public String getName() { + return this.name != null ? this.name : getClass().getSimpleName(); + } + + /** + * Sets the authentication handler name. Authentication handler names SHOULD be unique within an + * {@link org.jasig.cas.authentication.AuthenticationManager}, and particular implementations + * may require uniqueness. Uniqueness is a best + * practice generally. + * + * @param name Handler name. + */ + public void setName(final String name) { + this.name = name; + } + + /** + * Sets principal factory to create principal objects. + * + * @param principalFactory the principal factory + */ + public void setPrincipalFactory(final PrincipalFactory principalFactory) { + this.principalFactory = principalFactory; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/AbstractCredential.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/AbstractCredential.java new file mode 100644 index 000000000000..5e89f6acb51f --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/AbstractCredential.java @@ -0,0 +1,68 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import java.io.Serializable; + +/** + * Base class for CAS credentials that are safe for long-term storage. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public abstract class AbstractCredential implements Credential, CredentialMetaData, Serializable { + + /** Serialization version marker. */ + private static final long serialVersionUID = 8196868021183513898L; + + /** + * @return The credential identifier. + */ + @Override + public String toString() { + return getId(); + } + + @Override + public boolean equals(final Object other) { + if (other == null) { + return false; + } + if (!(other instanceof Credential)) { + return false; + } + if (other == this) { + return true; + } + final EqualsBuilder builder = new EqualsBuilder(); + builder.append(getId(), ((Credential) other).getId()); + return builder.isEquals(); + } + + @Override + public int hashCode() { + final HashCodeBuilder builder = new HashCodeBuilder(11, 41); + builder.append(getClass().getName()); + builder.append(getId()); + return builder.toHashCode(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/AcceptAnyAuthenticationPolicyFactory.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/AcceptAnyAuthenticationPolicyFactory.java new file mode 100644 index 000000000000..6da75984d5b1 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/AcceptAnyAuthenticationPolicyFactory.java @@ -0,0 +1,46 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.services.ServiceContext; + +/** + * Produces authentication policies that passively satisfy any given {@link Authentication}. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class AcceptAnyAuthenticationPolicyFactory implements ContextualAuthenticationPolicyFactory { + + @Override + public ContextualAuthenticationPolicy createPolicy(final ServiceContext context) { + return new ContextualAuthenticationPolicy() { + + @Override + public ServiceContext getContext() { + return context; + } + + @Override + public boolean isSatisfiedBy(final Authentication authentication) { + return true; + } + }; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/AcceptUsersAuthenticationHandler.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/AcceptUsersAuthenticationHandler.java new file mode 100644 index 000000000000..ad36e58ea7dc --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/AcceptUsersAuthenticationHandler.java @@ -0,0 +1,82 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler; + +import javax.security.auth.login.AccountNotFoundException; +import javax.security.auth.login.FailedLoginException; +import javax.validation.constraints.NotNull; +import java.security.GeneralSecurityException; +import java.util.Collections; +import java.util.Map; + +/** + * Handler that contains a list of valid users and passwords. Useful if there is + * a small list of users that we wish to allow. An example use case may be if + * there are existing handlers that make calls to LDAP, etc. but there is a need + * for additional users we don't want in LDAP. With the chain of command + * processing of handlers, this handler could be added to check before LDAP and + * provide the list of additional users. The list of acceptable users is stored + * in a map. The key of the map is the username and the password is the object + * retrieved from doing map.get(KEY). + *

+ * Note that this class makes an unmodifiable copy of whatever map is provided + * to it. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * + * @since 3.0.0 + */ +public class AcceptUsersAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler { + + /** The list of users we will accept. */ + @NotNull + private Map users; + + /** + * {@inheritDoc} + **/ + @Override + protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential) + throws GeneralSecurityException, PreventedException { + + final String username = credential.getUsername(); + final String cachedPassword = this.users.get(username); + + if (cachedPassword == null) { + logger.debug("{} was not found in the map.", username); + throw new AccountNotFoundException(username + " not found in backing map."); + } + + final String encodedPassword = this.getPasswordEncoder().encode(credential.getPassword()); + if (!cachedPassword.equals(encodedPassword)) { + throw new FailedLoginException(); + } + return createHandlerResult(credential, this.principalFactory.createPrincipal(username), null); + } + + /** + * @param users The users to set. + */ + public final void setUsers(final Map users) { + this.users = Collections.unmodifiableMap(users); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/AccountDisabledException.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/AccountDisabledException.java new file mode 100644 index 000000000000..56c7be7a6cff --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/AccountDisabledException.java @@ -0,0 +1,48 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import javax.security.auth.login.AccountException; + +/** + * Describes an authentication error condition where a user account has been administratively disabled. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class AccountDisabledException extends AccountException { + + /** Serialization metadata. */ + private static final long serialVersionUID = 7487835035108753209L; + + /** + * Instantiates a new account disabled exception. + */ + public AccountDisabledException() { + } + + /** + * Instantiates a new account disabled exception. + * + * @param msg the msg + */ + public AccountDisabledException(final String msg) { + super(msg); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/AccountPasswordMustChangeException.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/AccountPasswordMustChangeException.java new file mode 100644 index 000000000000..86a8d7c4b644 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/AccountPasswordMustChangeException.java @@ -0,0 +1,49 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import javax.security.auth.login.CredentialExpiredException; + +/** + * Describes an authentication error condition where a user account's password must change before login. + * + * @author Misagh Moayyed + * @since 4.0.0 + */ +public class AccountPasswordMustChangeException extends CredentialExpiredException { + + /** Serialization metadata. */ + private static final long serialVersionUID = 7487835035108753209L; + + /** + * Instantiates a new account password must change exception. + */ + public AccountPasswordMustChangeException() { + } + + /** + * Instantiates a new account password must change exception. + * + * @param msg the msg + */ + public AccountPasswordMustChangeException(final String msg) { + super(msg); + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/AllAuthenticationPolicy.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/AllAuthenticationPolicy.java new file mode 100644 index 000000000000..fafbee5c5583 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/AllAuthenticationPolicy.java @@ -0,0 +1,33 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +/** + * Authentication security policy that is satisfied iff all given credentials are successfully authenticated. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class AllAuthenticationPolicy implements AuthenticationPolicy { + + @Override + public boolean isSatisfiedBy(final Authentication authn) { + return authn.getSuccesses().size() == authn.getCredentials().size(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/AnyAuthenticationPolicy.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/AnyAuthenticationPolicy.java new file mode 100644 index 000000000000..8ccea3200589 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/AnyAuthenticationPolicy.java @@ -0,0 +1,50 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +/** + * Authentication policy that is satisfied by at least one successfully authenticated credential. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class AnyAuthenticationPolicy implements AuthenticationPolicy { + + /** Flag to try all credentials before policy is satisfied. Defaults to false.*/ + private boolean tryAll; + + /** + * Sets the flag to try all credentials before the policy is satisfied. + * This flag is disabled by default such that the policy is satisfied immediately upon the first + * successfully authenticated credential. Defaults to false. + * + * @param tryAll True to force all credentials to be authenticated, false otherwise. + */ + public void setTryAll(final boolean tryAll) { + this.tryAll = tryAll; + } + + @Override + public boolean isSatisfiedBy(final Authentication authn) { + if (this.tryAll) { + return authn.getCredentials().size() == authn.getSuccesses().size() + authn.getFailures().size(); + } + return authn.getSuccesses().size() > 0; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/BasicCredentialMetaData.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/BasicCredentialMetaData.java new file mode 100644 index 000000000000..3824c1f1ae49 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/BasicCredentialMetaData.java @@ -0,0 +1,89 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import java.io.Serializable; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +/** + * Basic credential metadata implementation that stores the original credential ID and the original credential type. + * This can be used as a simple converter for any {@link Credential} that doesn't implement {@link CredentialMetaData}. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class BasicCredentialMetaData implements CredentialMetaData, Serializable { + + /** Serialization version marker. */ + private static final long serialVersionUID = 4929579849241505377L; + + /** Credential type unique identifier. */ + private final String id; + + /** Type of original credential. */ + private Class credentialClass; + + /** No-arg constructor for serialization support. */ + private BasicCredentialMetaData() { + this.id = null; + } + + /** + * Creates a new instance from the given credential. + * + * @param credential Credential for which metadata should be created. + */ + public BasicCredentialMetaData(final Credential credential) { + this.id = credential.getId(); + this.credentialClass = credential.getClass(); + } + + @Override + public String getId() { + return this.id; + } + + /** + * Gets the type of the original credential. + * + * @return Non-null credential class. + */ + public Class getCredentialClass() { + return this.credentialClass; + } + + @Override + public int hashCode() { + return new HashCodeBuilder(19, 21).append(this.id).append(this.credentialClass).toHashCode(); + } + + @Override + public boolean equals(final Object other) { + if (!(other instanceof BasicCredentialMetaData)) { + return false; + } + final BasicCredentialMetaData md = (BasicCredentialMetaData) other; + final EqualsBuilder builder = new EqualsBuilder(); + builder.append(this.id, md.id); + builder.append(this.credentialClass, md.credentialClass); + return builder.isEquals(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/CacheCredentialsMetaDataPopulator.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/CacheCredentialsMetaDataPopulator.java new file mode 100644 index 000000000000..20db2d28cac5 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/CacheCredentialsMetaDataPopulator.java @@ -0,0 +1,50 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * We utilize the {@link org.jasig.cas.authentication.AuthenticationMetaDataPopulator} to retrieve and store + * the password as an authentication attribute under the key + * {@link UsernamePasswordCredential#AUTHENTICATION_ATTRIBUTE_PASSWORD}. + * + * @author Misagh Moayyed + * @since 4.1 + */ +public final class CacheCredentialsMetaDataPopulator implements AuthenticationMetaDataPopulator { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public void populateAttributes(final AuthenticationBuilder builder, final Credential credential) { + logger.debug("Processing request to capture the credential for [{}]", credential.getId()); + final UsernamePasswordCredential c = (UsernamePasswordCredential) credential; + builder.addAttribute(UsernamePasswordCredential.AUTHENTICATION_ATTRIBUTE_PASSWORD, c.getPassword()); + logger.debug("Encrypted credential is added as the authentication attribute [{}] to the authentication", + UsernamePasswordCredential.AUTHENTICATION_ATTRIBUTE_PASSWORD); + + } + + @Override + public boolean supports(final Credential credential) { + return credential instanceof UsernamePasswordCredential; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/DefaultAuthenticationBuilder.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/DefaultAuthenticationBuilder.java new file mode 100644 index 000000000000..d296514a7662 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/DefaultAuthenticationBuilder.java @@ -0,0 +1,307 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.authentication.principal.Principal; +import org.joda.time.DateTime; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Constructs immutable {@link Authentication} objects using the builder pattern. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class DefaultAuthenticationBuilder implements AuthenticationBuilder { + /** Authenticated principal. */ + private Principal principal; + + /** Credential metadata. */ + private final List credentials = new ArrayList<>(); + + /** Authentication metadata attributes. */ + private final Map attributes = new LinkedHashMap<>(); + + /** Map of handler names to authentication successes. */ + private final Map successes = new LinkedHashMap<>(); + + /** Map of handler names to authentication failures. */ + private final Map> failures = new LinkedHashMap<>(); + + /** Authentication date. */ + private DateTime authenticationDate; + + /** + * Creates a new instance using the current date for the authentication date. + */ + public DefaultAuthenticationBuilder() { + authenticationDate = new DateTime(); + } + + /** + * Creates a new instance using the current date for the authentication date and the given + * principal for the authenticated principal. + * + * @param p Authenticated principal. + */ + public DefaultAuthenticationBuilder(final Principal p) { + this(); + this.principal = p; + } + + /** + * Gets the authentication date. + * + * @return Authentication date. + */ + public DateTime getAuthenticationDate() { + return this.authenticationDate == null ? null : new DateTime(this.authenticationDate); + } + + /** + * Sets the authentication date and returns this instance. + * + * @param d Authentication date. + * + * @return This builder instance. + */ + @Override + public AuthenticationBuilder setAuthenticationDate(final Date d) { + this.authenticationDate = new DateTime(d); + return this; + } + + /** + * Gets the authenticated principal. + * + * @return Principal. + */ + @Override + public Principal getPrincipal() { + return this.principal; + } + + /** + * Sets the principal returns this instance. + * + * @param p Authenticated principal. + * + * @return This builder instance. + */ + @Override + public AuthenticationBuilder setPrincipal(final Principal p) { + this.principal = p; + return this; + } + + /** + * Gets the list of credentials that were attempted to be authenticated. + * + * @return Non-null list of credentials that attempted authentication. + */ + public List getCredentials() { + return this.credentials; + } + + /** + * Sets the list of metadata about credentials presented for authentication. + * + * @param credentials Non-null list of credential metadata. + * + * @return This builder instance. + */ + public AuthenticationBuilder setCredentials(final List credentials) { + Assert.notNull(credentials, "Credential cannot be null"); + this.credentials.clear(); + this.credentials.addAll(credentials); + return this; + } + + /** + * Adds metadata about a credential presented for authentication. + * + * @param credential Credential metadata. + * + * @return This builder instance. + */ + @Override + public AuthenticationBuilder addCredential(final CredentialMetaData credential) { + this.credentials.add(credential); + return this; + } + + /** + * Gets the authentication attribute map. + * + * @return Non-null authentication attribute map. + */ + public Map getAttributes() { + return this.attributes; + } + + /** + * Sets the authentication metadata attributes. + * + * @param attributes Non-null map of authentication metadata attributes. + * + * @return This builder instance. + */ + @Override + public AuthenticationBuilder setAttributes(final Map attributes) { + Assert.notNull(attributes, "Attributes cannot be null"); + this.attributes.clear(); + for (final Map.Entry entry : attributes.entrySet()) { + this.attributes.put(entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * Adds an authentication metadata attribute key-value pair. + * + * @param key Authentication attribute key. + * @param value Authentication attribute value. + * + * @return This builder instance. + */ + @Override + public AuthenticationBuilder addAttribute(final String key, final Object value) { + this.attributes.put(key, value); + return this; + } + + /** + * Gets the authentication success map. + * + * @return Non-null map of handler names to successful handler authentication results. + */ + @Override + public Map getSuccesses() { + return this.successes; + } + + /** + * Sets the authentication handler success map. + * + * @param successes Non-null map of handler names to successful handler authentication results. + * + * @return This builder instance. + */ + public AuthenticationBuilder setSuccesses(final Map successes) { + Assert.notNull(successes, "Successes cannot be null"); + this.successes.clear(); + for (final Map.Entry entry : successes.entrySet()) { + this.successes.put(entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * Adds an authentication success to the map of handler names to successful authentication handler results. + * + * @param key Authentication handler name. + * @param value Successful authentication handler result produced by handler of given name. + * + * @return This builder instance. + */ + @Override + public AuthenticationBuilder addSuccess(final String key, final HandlerResult value) { + this.successes.put(key, value); + return this; + } + + /** + * Gets the authentication failure map. + * + * @return Non-null authentication failure map. + */ + @Override + public Map> getFailures() { + return this.failures; + } + + /** + * Sets the authentication handler failure map. + * + * @param failures Non-null map of handler name to authentication failures. + * + * @return This builder instance. + */ + public AuthenticationBuilder setFailures(final Map> failures) { + Assert.notNull(failures, "Failures cannot be null"); + this.failures.clear(); + for (final Map.Entry> entry : failures.entrySet()) { + this.failures.put(entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * Adds an authentication failure to the map of handler names to the authentication handler failures. + * + * @param key Authentication handler name. + * @param value Exception raised on handler failure to authenticate credential. + * + * @return This builder instance. + */ + @Override + public AuthenticationBuilder addFailure(final String key, final Class value) { + this.failures.put(key, value); + return this; + } + + /** + * Creates an immutable authentication instance from builder data. + * + * @return Immutable authentication. + */ + @Override + public Authentication build() { + return new ImmutableAuthentication( + this.authenticationDate, + this.credentials, + this.principal, + this.attributes, + this.successes, + this.failures); + } + + /** + * Creates a new builder initialized with data from the given authentication source. + * + * @param source Authentication source. + * + * @return New builder instance initialized with all fields in the given authentication source. + */ + public static AuthenticationBuilder newInstance(final Authentication source) { + final DefaultAuthenticationBuilder builder = new DefaultAuthenticationBuilder(source.getPrincipal()); + builder.setAuthenticationDate(source.getAuthenticationDate()); + builder.setCredentials(source.getCredentials()); + builder.setSuccesses(source.getSuccesses()); + builder.setFailures(source.getFailures()); + builder.setAttributes(source.getAttributes()); + return builder; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/DefaultHandlerResult.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/DefaultHandlerResult.java new file mode 100644 index 000000000000..fcb124ab682b --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/DefaultHandlerResult.java @@ -0,0 +1,169 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import java.util.Collections; +import java.util.List; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.jasig.cas.MessageDescriptor; +import org.jasig.cas.authentication.principal.Principal; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Contains information about a successful authentication produced by an {@link AuthenticationHandler}. + * Handler results are naturally immutable since they contain sensitive information that should not be modified outside + * the {@link AuthenticationHandler} that produced it. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class DefaultHandlerResult implements HandlerResult { + + /** Serialization support. */ + private static final long serialVersionUID = -3113998493287982485L; + + /** The name of the authentication handler that successfully authenticated a credential. */ + private String handlerName; + + /** Credential meta data. */ + private CredentialMetaData credentialMetaData; + + /** Resolved principal for authenticated credential. */ + private Principal principal; + + /** List of warnings issued by the authentication source while authenticating the credential. */ + private List warnings; + + /** No-arg constructor for serialization support. */ + private DefaultHandlerResult() {} + + /** + * Instantiates a new handler result. + * + * @param source the source + * @param metaData the meta data + */ + public DefaultHandlerResult(final AuthenticationHandler source, final CredentialMetaData metaData) { + this(source, metaData, null, null); + } + + /** + * Instantiates a new handler result. + * + * @param source the source + * @param metaData the meta data + * @param p the p + */ + public DefaultHandlerResult(final AuthenticationHandler source, final CredentialMetaData metaData, final Principal p) { + this(source, metaData, p, null); + } + + /** + * Instantiates a new handler result. + * + * @param source the source + * @param metaData the meta data + * @param warnings the warnings + */ + public DefaultHandlerResult( + final AuthenticationHandler source, final CredentialMetaData metaData, final List warnings) { + this(source, metaData, null, warnings); + } + + /** + * Instantiates a new handler result. + * + * @param source the source + * @param metaData the meta data + * @param p the p + * @param warnings the warnings + */ + public DefaultHandlerResult( + final AuthenticationHandler source, + final CredentialMetaData metaData, + final Principal p, + final List warnings) { + Assert.notNull(source, "Source cannot be null."); + Assert.notNull(metaData, "Credential metadata cannot be null."); + this.handlerName = source.getName(); + if (!StringUtils.hasText(this.handlerName)) { + this.handlerName = source.getClass().getSimpleName(); + } + this.credentialMetaData = metaData; + this.principal = p; + this.warnings = warnings; + } + + @Override + public String getHandlerName() { + return this.handlerName; + } + + @Override + public CredentialMetaData getCredentialMetaData() { + return this.credentialMetaData; + } + + @Override + public Principal getPrincipal() { + return this.principal; + } + + @Override + public List getWarnings() { + return this.warnings == null + ? Collections.emptyList() + : Collections.unmodifiableList(this.warnings); + } + + @Override + public int hashCode() { + final HashCodeBuilder builder = new HashCodeBuilder(109, 31); + builder.append(this.handlerName); + builder.append(this.credentialMetaData); + builder.append(this.principal); + builder.append(this.warnings); + return builder.toHashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof DefaultHandlerResult)) { + return false; + } + if (obj == this) { + return true; + } + final DefaultHandlerResult other = (DefaultHandlerResult) obj; + final EqualsBuilder builder = new EqualsBuilder(); + builder.append(this.handlerName, other.handlerName); + builder.append(this.credentialMetaData, other.credentialMetaData); + builder.append(this.principal, other.principal); + builder.append(this.warnings, other.warnings); + return builder.isEquals(); + } + + @Override + public String toString() { + return this.handlerName + ':' + this.credentialMetaData; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/FileTrustStoreSslSocketFactory.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/FileTrustStoreSslSocketFactory.java new file mode 100644 index 000000000000..f49df5da3214 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/FileTrustStoreSslSocketFactory.java @@ -0,0 +1,300 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLContexts; +import org.apache.http.conn.ssl.TrustStrategy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509KeyManager; +import javax.net.ssl.X509TrustManager; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.net.Socket; +import java.security.KeyStore; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * The SSL socket factory that loads the SSL context from a custom + * truststore file strictly used ssl handshakes for proxy authentication. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class FileTrustStoreSslSocketFactory extends SSLConnectionSocketFactory { + + private static final Logger LOGGER = LoggerFactory.getLogger(FileTrustStoreSslSocketFactory.class); + + /** + * Instantiates a new trusted proxy authentication trust store ssl socket factory. + * Defaults to TLSv1 and {@link SSLConnectionSocketFactory#BROWSER_COMPATIBLE_HOSTNAME_VERIFIER} + * for the supported protocols and hostname verification. + * @param trustStoreFile the trust store file + * @param trustStorePassword the trust store password + */ + public FileTrustStoreSslSocketFactory(final File trustStoreFile, final String trustStorePassword) { + this(trustStoreFile, trustStorePassword, KeyStore.getDefaultType()); + } + + + /** + * Instantiates a new trusted proxy authentication trust store ssl socket factory. + * @param trustStoreFile the trust store file + * @param trustStorePassword the trust store password + * @param trustStoreType the trust store type + */ + public FileTrustStoreSslSocketFactory(final File trustStoreFile, final String trustStorePassword, + final String trustStoreType) { + super(getTrustedSslContext(trustStoreFile, trustStorePassword, trustStoreType)); + } + + /** + * Gets the trusted ssl context. + * + * @param trustStoreFile the trust store file + * @param trustStorePassword the trust store password + * @param trustStoreType the trust store type + * @return the trusted ssl context + */ + private static SSLContext getTrustedSslContext(final File trustStoreFile, final String trustStorePassword, + final String trustStoreType) { + try { + + if (!trustStoreFile.exists() || !trustStoreFile.canRead()) { + throw new FileNotFoundException("Truststore file cannot be located at " + trustStoreFile.getCanonicalPath()); + } + + final KeyStore casTrustStore = KeyStore.getInstance(trustStoreType); + final char[] trustStorePasswordCharArray = trustStorePassword.toCharArray(); + + try (final FileInputStream casStream = new FileInputStream(trustStoreFile)) { + casTrustStore.load(casStream, trustStorePasswordCharArray); + } + + final String defaultAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); + final X509KeyManager customKeyManager = getKeyManager("PKIX", casTrustStore, trustStorePasswordCharArray); + final X509KeyManager jvmKeyManager = getKeyManager(defaultAlgorithm, null, null); + final X509TrustManager customTrustManager = getTrustManager("PKIX", casTrustStore); + final X509TrustManager jvmTrustManager = getTrustManager(defaultAlgorithm, null); + + final KeyManager[] keyManagers = { + new CompositeX509KeyManager(Arrays.asList(jvmKeyManager, customKeyManager)) + }; + final TrustManager[] trustManagers = { + new CompositeX509TrustManager(Arrays.asList(jvmTrustManager, customTrustManager)) + }; + + final SSLContext context = SSLContexts.custom().useSSL().build(); + context.init(keyManagers, trustManagers, null); + return context; + + } catch (final Exception e) { + LOGGER.error(e.getMessage(), e); + throw new RuntimeException(e); + } + } + + /** + * Gets key manager. + * + * @param algorithm the algorithm + * @param keystore the keystore + * @param password the password + * @return the key manager + * @throws Exception the exception + */ + private static X509KeyManager getKeyManager(final String algorithm, final KeyStore keystore, + final char[] password) throws Exception { + final KeyManagerFactory factory = KeyManagerFactory.getInstance(algorithm); + factory.init(keystore, password); + return (X509KeyManager) factory.getKeyManagers()[0]; + } + + /** + * Gets trust manager. + * + * @param algorithm the algorithm + * @param keystore the keystore + * @return the trust manager + * @throws Exception the exception + */ + private static X509TrustManager getTrustManager(final String algorithm, + final KeyStore keystore) throws Exception{ + final TrustManagerFactory factory = TrustManagerFactory.getInstance(algorithm); + factory.init(keystore); + return (X509TrustManager) factory.getTrustManagers()[0]; + } + + private static class DoesNotTrustStrategy implements TrustStrategy { + @Override + public boolean isTrusted(final X509Certificate[] x509Certificates, final String s) throws CertificateException { + return false; + } + } + + private static class CompositeX509KeyManager implements X509KeyManager { + + private final List keyManagers; + + /** + * Represents an ordered list of {@link X509KeyManager}s with most-preferred managers first. + * @param keyManagers list of key managers + */ + public CompositeX509KeyManager(final List keyManagers) { + this.keyManagers = keyManagers; + } + + @Override + public String chooseClientAlias(final String[] keyType, final Principal[] issuers, final Socket socket) { + for (final X509KeyManager keyManager : keyManagers) { + final String alias = keyManager.chooseClientAlias(keyType, issuers, socket); + if (alias != null) { + return alias; + } + } + return null; + } + + + @Override + public String chooseServerAlias(final String keyType, final Principal[] issuers, final Socket socket) { + for (final X509KeyManager keyManager : keyManagers) { + final String alias = keyManager.chooseServerAlias(keyType, issuers, socket); + if (alias != null) { + return alias; + } + } + return null; + } + + + @Override + public PrivateKey getPrivateKey(final String alias) { + for (final X509KeyManager keyManager : keyManagers) { + final PrivateKey privateKey = keyManager.getPrivateKey(alias); + if (privateKey != null) { + return privateKey; + } + } + return null; + } + + + @Override + public X509Certificate[] getCertificateChain(final String alias) { + for (final X509KeyManager keyManager : keyManagers) { + final X509Certificate[] chain = keyManager.getCertificateChain(alias); + if (chain != null && chain.length > 0) { + return chain; + } + } + return null; + } + + @Override + public String[] getClientAliases(final String keyType, final Principal[] issuers) { + final List aliases = new ArrayList<>(); + for (final X509KeyManager keyManager : keyManagers) { + final List list = Arrays.asList(keyManager.getClientAliases(keyType, issuers)); + aliases.addAll(list); + } + return aliases.toArray(new String[] {}); + } + + @Override + public String[] getServerAliases(final String keyType, final Principal[] issuers) { + final List aliases = new ArrayList<>(); + for (final X509KeyManager keyManager : keyManagers) { + final List list = Arrays.asList(keyManager.getServerAliases(keyType, issuers)); + aliases.addAll(list); + } + return aliases.toArray(new String[] {}); + } + + } + + /** + * Represents an ordered list of {@link X509TrustManager}s with additive trust. If any one of the + * composed managers trusts a certificate chain, then it is trusted by the composite manager. + */ + private static class CompositeX509TrustManager implements X509TrustManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(CompositeX509TrustManager.class); + + private final List trustManagers; + + /** + * Instantiates a new Composite x 509 trust manager. + * + * @param trustManagers the trust managers + */ + public CompositeX509TrustManager(final List trustManagers) { + this.trustManagers = trustManagers; + } + + @Override + public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { + for (final X509TrustManager trustManager : trustManagers) { + try { + trustManager.checkClientTrusted(chain, authType); + return; + } catch (final CertificateException e) { + LOGGER.debug(e.getMessage(), e); + } + } + throw new CertificateException("None of the TrustManagers trust this certificate chain"); + } + + @Override + public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { + for (final X509TrustManager trustManager : trustManagers) { + try { + trustManager.checkServerTrusted(chain, authType); + return; + } catch (final CertificateException e) { + LOGGER.debug(e.getMessage(), e); + } + } + throw new CertificateException("None of the TrustManagers trust this certificate chain"); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + final List certificates = new ArrayList<>(); + for (final X509TrustManager trustManager : trustManagers) { + final List list = Arrays.asList(trustManager.getAcceptedIssuers()); + certificates.addAll(list); + } + return certificates.toArray(new X509Certificate[] {}); + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/HttpBasedServiceCredential.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/HttpBasedServiceCredential.java new file mode 100644 index 000000000000..702bfe495518 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/HttpBasedServiceCredential.java @@ -0,0 +1,127 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.jasig.cas.services.RegisteredService; + +import javax.validation.constraints.NotNull; +import java.net.URL; + +/** + * A credential representing an HTTP endpoint given by a URL. Authenticating the credential usually involves + * contacting the endpoint via the URL and observing the resulting connection (e.g. SSL certificate) and response + * (e.g. status, headers). + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.0.0 + */ +public class HttpBasedServiceCredential extends AbstractCredential { + + /** Unique Serializable ID. */ + private static final long serialVersionUID = 1492607216336354503L; + + /** The callbackURL to check that identifies the application. */ + private final URL callbackUrl; + + /** String form of callbackUrl. */ + private final String callbackUrlAsString; + + /** The registered service associated with this callback. **/ + private final RegisteredService service; + + /** + * Empty constructor used by Kryo for de-serialization. + */ + protected HttpBasedServiceCredential() { + this.callbackUrl = null; + this.callbackUrlAsString = null; + this.service = null; + } + + /** + * Creates a new instance for an HTTP endpoint located at the given URL. + * + * @param callbackUrl Non-null URL that will be contacted to validate the credential. + * @param service The registered service associated with this callback. + */ + public HttpBasedServiceCredential(@NotNull final URL callbackUrl, @NotNull final RegisteredService service) { + this.callbackUrl = callbackUrl; + this.callbackUrlAsString = callbackUrl.toExternalForm(); + this.service = service; + } + + /** + * {@inheritDoc} + */ + @Override + public String getId() { + return this.callbackUrlAsString; + } + + /** + * @return Returns the callbackUrl. + */ + public final URL getCallbackUrl() { + return this.callbackUrl; + } + + /** + * Gets service associated with credentials. + * + * @return the service + */ + public final RegisteredService getService() { + return this.service; + } + + @Override + public int hashCode() { + final HashCodeBuilder bldr = new HashCodeBuilder(13, 133); + return bldr.append(this.callbackUrlAsString) + .append(this.service) + .toHashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final HttpBasedServiceCredential other = (HttpBasedServiceCredential) obj; + if (this.callbackUrlAsString == null) { + if (other.callbackUrlAsString != null) { + return false; + } + } else if (!this.callbackUrlAsString.equals(other.callbackUrlAsString)) { + return false; + } + + return (this.service.equals(other.getService())); + } + + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/ImmutableAuthentication.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/ImmutableAuthentication.java new file mode 100644 index 000000000000..e399c7ee7ec2 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/ImmutableAuthentication.java @@ -0,0 +1,233 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.jasig.cas.authentication.principal.Principal; +import org.joda.time.DateTime; +import org.springframework.util.Assert; + +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * Immutable authentication event whose attributes may not change after creation. + * This class is designed for serialization and is suitable for long-term storage. + * + * @author Dmitriy Kopylenko + * @author Scott Battaglia + * @author Marvin S. Addison + * + * @since 3.0.0 + */ +public final class ImmutableAuthentication implements Authentication { + + /** UID for serializing. */ + private static final long serialVersionUID = 3206127526058061391L; + + /** Authentication date stamp. */ + private final long authenticationDate; + + /** List of metadata about credentials presented at authentication. */ + private final List credentials; + + /** Authenticated principal. */ + private final Principal principal; + + /** Authentication metadata attributes. */ + private final Map attributes; + + /** Map of handler name to handler authentication success event. */ + private final Map successes; + + /** Map of handler name to handler authentication failure cause. */ + private final Map> failures; + + /** No-arg constructor for serialization support. */ + private ImmutableAuthentication() { + this.authenticationDate = 0; + this.credentials = null; + this.principal = null; + this.attributes = null; + this.successes = null; + this.failures = null; + } + + /** + * Creates a new instance with the given data. + * + * @param date Non-null authentication date. + * @param credentials Non-null list of credential metadata containing at least one entry. + * @param principal Non-null authenticated principal. + * @param attributes Nullable map of authentication metadata. + * @param successes Non-null map of authentication successes containing at least one entry. + * @param failures Nullable map of authentication failures. + */ + public ImmutableAuthentication( + final DateTime date, + final List credentials, + final Principal principal, + final Map attributes, + final Map successes, + final Map> failures) { + + Assert.notNull(date, "Date cannot be null"); + Assert.notNull(credentials, "Credential cannot be null"); + Assert.notNull(principal, "Principal cannot be null"); + Assert.notNull(successes, "Successes cannot be null"); + Assert.notEmpty(credentials, "Credential cannot be empty"); + Assert.notEmpty(successes, "Successes cannot be empty"); + + this.authenticationDate = date.toDate().getTime(); + this.credentials = credentials; + this.principal = principal; + this.attributes = attributes.isEmpty() ? null : attributes; + this.successes = successes; + this.failures = failures.isEmpty() ? null : failures; + } + + @Override + public Principal getPrincipal() { + return this.principal; + } + + public Date getAuthenticationDate() { + return new ImmutableDate(this.authenticationDate); + } + + @Override + public Map getAttributes() { + return wrap(this.attributes); + } + + @Override + public List getCredentials() { + return Collections.unmodifiableList(this.credentials); + } + + @Override + public Map getSuccesses() { + return Collections.unmodifiableMap(this.successes); + } + + @Override + public Map> getFailures() { + return wrap(this.failures); + } + + @Override + public int hashCode() { + final HashCodeBuilder builder = new HashCodeBuilder(97, 31); + builder.append(this.principal); + builder.append(this.authenticationDate); + builder.append(this.attributes); + builder.append(this.credentials); + builder.append(this.successes); + builder.append(this.failures); + return builder.toHashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof Authentication)) { + return false; + } + if (obj == this) { + return true; + } + final Authentication other = (Authentication) obj; + final EqualsBuilder builder = new EqualsBuilder(); + builder.append(this.principal, other.getPrincipal()); + builder.append(this.credentials, other.getCredentials()); + builder.append(this.successes, other.getSuccesses()); + builder.append(this.authenticationDate, other.getAuthenticationDate().getTime()); + builder.append(wrap(this.attributes), other.getAttributes()); + builder.append(wrap(this.failures), other.getFailures()); + return builder.isEquals(); + } + + /** + * Wraps a possibly null map in an immutable wrapper. + * + * @param the key type + * @param the value type + * @param source Nullable map to wrap. + * @return {@link Collections#unmodifiableMap(java.util.Map)} if given map is not null, otherwise + * {@link java.util.Collections#emptyMap()}. + */ + private static Map wrap(final Map source) { + if (source != null) { + return Collections.unmodifiableMap(source); + } + return Collections.emptyMap(); + } + + /** + * Immutable date implementation that throws {@link UnsupportedOperationException} for setter methods. + */ + private static final class ImmutableDate extends Date { + + private static final long serialVersionUID = 6275827030191703183L; + + /** No-arg constructor for serialization support. */ + private ImmutableDate() {} + + /** + * Creates a new instance with the given epoch time in milliseconds. + * + * @param instant Milliseconds since the Unix epoch. + */ + public ImmutableDate(final long instant) { + super(instant); + } + + @Override + public void setYear(final int year) { + throw new UnsupportedOperationException(); + } + + @Override + public void setDate(final int date) { + throw new UnsupportedOperationException(); + } + + @Override + public void setHours(final int hours) { + throw new UnsupportedOperationException(); + } + + @Override + public void setMinutes(final int minutes) { + throw new UnsupportedOperationException(); + } + + @Override + public void setSeconds(final int seconds) { + throw new UnsupportedOperationException(); + } + + @Override + public void setTime(final long time) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/InvalidLoginLocationException.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/InvalidLoginLocationException.java new file mode 100644 index 000000000000..73070c2e8031 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/InvalidLoginLocationException.java @@ -0,0 +1,49 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import javax.security.auth.login.AccountException; + +/** + * Describes an error condition where authentication occurs from a location that is disallowed by security policy + * applied to the underlying user account. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class InvalidLoginLocationException extends AccountException { + + private static final long serialVersionUID = 5745711263227480194L; + + /** + * Instantiates a new invalid login location exception. + */ + public InvalidLoginLocationException() { + super(); + } + + /** + * Instantiates a new invalid login location exception. + * + * @param message the message + */ + public InvalidLoginLocationException(final String message) { + super(message); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/InvalidLoginTimeException.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/InvalidLoginTimeException.java new file mode 100644 index 000000000000..04abb67acc76 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/InvalidLoginTimeException.java @@ -0,0 +1,50 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import javax.security.auth.login.AccountException; + +/** + * Describes an error condition where authentication occurs at a time that is disallowed by security policy + * applied to the underlying user account. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class InvalidLoginTimeException extends AccountException { + + private static final long serialVersionUID = -6699752791525619208L; + + /** + * Instantiates a new invalid login time exception. + */ + public InvalidLoginTimeException() { + super(); + } + + /** + * Instantiates a new invalid login time exception. + * + * @param message the message + */ + public InvalidLoginTimeException(final String message) { + super(message); + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/MixedPrincipalException.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/MixedPrincipalException.java new file mode 100644 index 000000000000..2ba1b1f0ba93 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/MixedPrincipalException.java @@ -0,0 +1,72 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.authentication.principal.Principal; + +/** + * Describes an error condition where non-identical principals have been resolved while authenticating + * multiple credentials. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class MixedPrincipalException extends PrincipalException { + + /** Serialization version marker. */ + private static final long serialVersionUID = -9040132618070273997L; + + /** First resolved principal. */ + private final Principal first; + + /** Second resolved principal. */ + private final Principal second; + + /** + * Creates a new instance from what would otherwise have been a successful authentication event and the two + * disparate principals resolved. + * + * @param authentication Authentication event. + * @param a First resolved principal. + * @param b Second resolved principal. + */ + public MixedPrincipalException(final Authentication authentication, final Principal a, final Principal b) { + super(a + " != " + b, authentication.getFailures(), authentication.getSuccesses()); + this.first = a; + this.second = b; + } + + /** + * Gets the first resolved principal. + * + * @return First resolved principal. + */ + public Principal getFirst() { + return this.first; + } + + /** + * Gets the second resolved principal. + * + * @return Second resolved principal. + */ + public Principal getSecond() { + return this.second; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/NotPreventedAuthenticationPolicy.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/NotPreventedAuthenticationPolicy.java new file mode 100644 index 000000000000..f7586c18ce92 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/NotPreventedAuthenticationPolicy.java @@ -0,0 +1,40 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +/** + * Authentication policy that defines success as at least one authentication success and no authentication attempts + * that were prevented by system errors. This policy may be a desirable alternative to {@link AnyAuthenticationPolicy} + * for cases where deployers wish to fail closed for indeterminate security events. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class NotPreventedAuthenticationPolicy extends AnyAuthenticationPolicy { + + @Override + public boolean isSatisfiedBy(final Authentication authentication) { + for (final String handler : authentication.getFailures().keySet()) { + if (authentication.getFailures().get(handler).isAssignableFrom(PreventedException.class)) { + return false; + } + } + return super.isSatisfiedBy(authentication); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/OneTimePasswordCredential.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/OneTimePasswordCredential.java new file mode 100644 index 000000000000..06f8ac69a92d --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/OneTimePasswordCredential.java @@ -0,0 +1,85 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +/** + * Describes a one-time-password credential that contains an optional unique identifier and required password. + * The primary difference between this component and {@link UsernamePasswordCredential} is that the username/ID is optional + * in the former and requisite in the latter. + *

+ * This class implements {@link CredentialMetaData} since the one-time-password is safe for long-term storage after + * authentication. Note that metadata is stored only _after_ authentication, at which time the OTP has already + * been consumed and by definition is no longer useful for authentication. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class OneTimePasswordCredential extends AbstractCredential { + + /** Serialization version marker. */ + private static final long serialVersionUID = 1892587671827699709L; + + /** One-time password. */ + private final String password; + + /** Optional unique identifier. */ + private String id; + + /** + * Creates a one-time-password with just a password. + * + * @param password Non-null cleartext one-time password value. + */ + public OneTimePasswordCredential(final String password) { + if (password == null) { + throw new IllegalArgumentException("One-time password cannot be null."); + } + this.password = password; + } + + + /** + * Creates a one-time-password with unique ID and password. + * + * @param id Identifier that is commonly used to look up one-time password in system of record. + * @param password Non-null cleartext one-time password value. + */ + public OneTimePasswordCredential(final String id, final String password) { + this(password); + this.id = id; + } + + /** + * Gets the cleartext one-time password value. + * + * @return Non-null one-time password. + */ + public String getPassword() { + return this.password; + } + + /** + * Gets the unique ID commonly used to look up a one-time password in a system of record. + * + * @return Possibly null unique ID. + */ + public String getId() { + return id; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/PolicyBasedAuthenticationManager.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/PolicyBasedAuthenticationManager.java new file mode 100644 index 000000000000..0ebd8cf0b0f5 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/PolicyBasedAuthenticationManager.java @@ -0,0 +1,297 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import com.codahale.metrics.annotation.Counted; +import com.codahale.metrics.annotation.Metered; +import com.codahale.metrics.annotation.Timed; +import org.jasig.inspektr.audit.annotation.Audit; +import org.jasig.cas.authentication.principal.NullPrincipal; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.PrincipalResolver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.Assert; + +import javax.validation.constraints.NotNull; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Provides an authenticaiton manager that is inherently aware of multiple credentials and supports pluggable + * security policy via the {@link AuthenticationPolicy} component. The authentication process is as follows: + * + *

    + *
  • For each given credential do the following: + *
      + *
    • Iterate over all configured authentication handlers.
    • + *
    • Attempt to authenticate a credential if a handler supports it.
    • + *
    • On success attempt to resolve a principal by doing the following: + *
        + *
      • Check whether a resolver is configured for the handler that authenticated the credential.
      • + *
      • If a suitable resolver is found, attempt to resolve the principal.
      • + *
      • If a suitable resolver is not found, use the principal resolved by the authentication handler.
      • + *
      + *
    • + *
    • Check whether the security policy (e.g. any, all) is satisfied. + *
        + *
      • If security policy is met return immediately.
      • + *
      • Continue if security policy is not met.
      • + *
      + *
    • + *
    + *
  • + *
  • + * After all credentials have been attempted check security policy again. + * Note there is an implicit security policy that requires at least one credential to be authenticated. + * Then the security policy given by {@link #setAuthenticationPolicy(AuthenticationPolicy)} is applied. + * In all cases {@link AuthenticationException} is raised if security policy is not met. + *
  • + *
+ * + * It is an error condition to fail to resolve a principal. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class PolicyBasedAuthenticationManager implements AuthenticationManager { + + /** Log instance for logging events, errors, warnings, etc. */ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + /** An array of AuthenticationAttributesPopulators. */ + @NotNull + private List authenticationMetaDataPopulators = + new ArrayList<>(); + + /** Authentication security policy. */ + @NotNull + private AuthenticationPolicy authenticationPolicy = new AnyAuthenticationPolicy(); + + /** Map of authentication handlers to resolvers to be used when handler does not resolve a principal. */ + @NotNull + private final Map handlerResolverMap; + + + /** + * Creates a new authentication manager with a varargs array of authentication handlers that are attempted in the + * listed order for supported credentials. This form may only be used by authentication handlers that + * resolve principals during the authentication process. + * + * @param handlers One or more authentication handlers. + */ + public PolicyBasedAuthenticationManager(final AuthenticationHandler ... handlers) { + this(Arrays.asList(handlers)); + } + + /** + * Creates a new authentication manager with a list of authentication handlers that are attempted in the + * listed order for supported credentials. This form may only be used by authentication handlers that + * resolve principals during the authentication process. + * + * @param handlers Non-null list of authentication handlers containing at least one entry. + */ + public PolicyBasedAuthenticationManager(final List handlers) { + Assert.notEmpty(handlers, "At least one authentication handler is required"); + this.handlerResolverMap = new LinkedHashMap<>( + handlers.size()); + for (final AuthenticationHandler handler : handlers) { + this.handlerResolverMap.put(handler, null); + } + } + + /** + * Creates a new authentication manager with a map of authentication handlers to the principal resolvers that + * should be used upon successful authentication if no principal is resolved by the authentication handler. If + * the order of evaluation of authentication handlers is important, a map that preserves insertion order + * (e.g. {@link LinkedHashMap}) should be used. + * + * @param map Non-null map of authentication handler to principal resolver containing at least one entry. + */ + public PolicyBasedAuthenticationManager(final Map map) { + Assert.notEmpty(map, "At least one authentication handler is required"); + this.handlerResolverMap = map; + } + + /** + * {@inheritDoc} + */ + @Override + @Audit( + action="AUTHENTICATION", + actionResolverName="AUTHENTICATION_RESOLVER", + resourceResolverName="AUTHENTICATION_RESOURCE_RESOLVER") + @Timed(name="AUTHENTICATE") + @Metered(name="AUTHENTICATE") + @Counted(name="AUTHENTICATE", monotonic=true) + public final Authentication authenticate(final Credential... credentials) throws AuthenticationException { + + final AuthenticationBuilder builder = authenticateInternal(credentials); + final Authentication authentication = builder.build(); + final Principal principal = authentication.getPrincipal(); + if (principal instanceof NullPrincipal) { + throw new UnresolvedPrincipalException(authentication); + } + + for (final HandlerResult result : authentication.getSuccesses().values()) { + builder.addAttribute(AUTHENTICATION_METHOD_ATTRIBUTE, result.getHandlerName()); + } + + logger.info("Authenticated {} with credentials {}.", principal, Arrays.asList(credentials)); + logger.debug("Attribute map for {}: {}", principal.getId(), principal.getAttributes()); + + for (final AuthenticationMetaDataPopulator populator : this.authenticationMetaDataPopulators) { + for (final Credential credential : credentials) { + if (populator.supports(credential)) { + populator.populateAttributes(builder, credential); + } + } + } + + return builder.build(); + } + + /** + * Sets the authentication metadata populators that will be applied to every successful authentication event. + * + * @param populators Non-null list of metadata populators. + */ + public final void setAuthenticationMetaDataPopulators(final List populators) { + this.authenticationMetaDataPopulators = populators; + } + + /** + * Sets the authentication policy used by this component. + * + * @param policy Non-null authentication policy. The default policy is {@link AnyAuthenticationPolicy}. + */ + public void setAuthenticationPolicy(final AuthenticationPolicy policy) { + this.authenticationPolicy = policy; + } + + /** + * Follows the same contract as {@link AuthenticationManager#authenticate(Credential...)}. + * + * @param credentials One or more credentials to authenticate. + * + * @return An authentication containing a resolved principal and metadata about successful and failed + * authentications. There SHOULD be a record of each attempted authentication, whether success or failure. + * + * @throws AuthenticationException When one or more credentials failed authentication such that security policy + * was not satisfied. + */ + protected AuthenticationBuilder authenticateInternal(final Credential... credentials) + throws AuthenticationException { + + final AuthenticationBuilder builder = new DefaultAuthenticationBuilder(NullPrincipal.getInstance()); + for (final Credential c : credentials) { + builder.addCredential(new BasicCredentialMetaData(c)); + } + boolean found; + Principal principal; + PrincipalResolver resolver; + for (final Credential credential : credentials) { + found = false; + for (final Map.Entry entry : this.handlerResolverMap.entrySet()) { + final AuthenticationHandler handler = entry.getKey(); + if (handler.supports(credential)) { + found = true; + try { + final HandlerResult result = handler.authenticate(credential); + builder.addSuccess(handler.getName(), result); + logger.info("{} successfully authenticated {}", handler.getName(), credential); + resolver = entry.getValue(); + if (resolver == null) { + principal = result.getPrincipal(); + logger.debug( + "No resolver configured for {}. Falling back to handler principal {}", + handler.getName(), + principal); + } else { + principal = resolvePrincipal(handler.getName(), resolver, credential); + } + // Must avoid null principal since AuthenticationBuilder/ImmutableAuthentication + // require principal to be non-null + if (principal != null) { + builder.setPrincipal(principal); + } + if (this.authenticationPolicy.isSatisfiedBy(builder.build())) { + return builder; + } + } catch (final GeneralSecurityException e) { + logger.info("{} failed authenticating {}", handler.getName(), credential); + logger.debug("{} exception details: {}", handler.getName(), e.getMessage()); + builder.addFailure(handler.getName(), e.getClass()); + } catch (final PreventedException e) { + logger.error("{}: {} (Details: {})", handler.getName(), e.getMessage(), e.getCause().getMessage()); + builder.addFailure(handler.getName(), e.getClass()); + } + } + } + if (!found) { + logger.warn( + "Cannot find authentication handler that supports {}, which suggests a configuration problem.", + credential); + } + } + // We apply an implicit security policy of at least one successful authentication + if (builder.getSuccesses().isEmpty()) { + throw new AuthenticationException(builder.getFailures(), builder.getSuccesses()); + } + // Apply the configured security policy + if (!this.authenticationPolicy.isSatisfiedBy(builder.build())) { + throw new AuthenticationException(builder.getFailures(), builder.getSuccesses()); + } + return builder; + } + + + /** + * Resolve principal. + * + * @param handlerName the handler name + * @param resolver the resolver + * @param credential the credential + * @return the principal + */ + protected Principal resolvePrincipal( + final String handlerName, final PrincipalResolver resolver, final Credential credential) { + if (resolver.supports(credential)) { + try { + final Principal p = resolver.resolve(credential); + logger.debug("{} resolved {} from {}", resolver, p, credential); + return p; + } catch (final Exception e) { + logger.error("{} failed to resolve principal from {}", resolver, credential, e); + } + } else { + logger.warn( + "{} is configured to use {} but it does not support {}, which suggests a configuration problem.", + handlerName, + resolver, + credential); + } + return null; + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/PrincipalException.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/PrincipalException.java new file mode 100644 index 000000000000..7f13f9b424a1 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/PrincipalException.java @@ -0,0 +1,47 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import java.util.Map; + +/** + * Describes a principal resolution error, which is a subcategory of authentication error. + * Principal resolution necessarily happens after successful authentication for a given credential. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class PrincipalException extends AuthenticationException { + + /** Serialization metadata. */ + private static final long serialVersionUID = -6590363469748313596L; + + /** + * Creates a new instance. + * @param message Error message. + * @param handlerErrors Map of handler names to errors. + * @param handlerSuccesses Map of handler names to authentication successes. + */ + public PrincipalException( + final String message, + final Map> handlerErrors, + final Map handlerSuccesses) { + super(message, handlerErrors, handlerSuccesses); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/RememberMeUsernamePasswordCredential.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/RememberMeUsernamePasswordCredential.java new file mode 100644 index 000000000000..d03e3bbbdc05 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/RememberMeUsernamePasswordCredential.java @@ -0,0 +1,70 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.apache.commons.lang3.builder.HashCodeBuilder; + +/** + * Handles both remember me services and username and password. + * + * @author Scott Battaglia + * @since 3.2.1 + * + */ +public class RememberMeUsernamePasswordCredential extends UsernamePasswordCredential implements RememberMeCredential { + + /** Unique Id for serialization. */ + private static final long serialVersionUID = -6710007659431302397L; + + private boolean rememberMe; + + public final boolean isRememberMe() { + return this.rememberMe; + } + + @Override + public int hashCode() { + return new HashCodeBuilder() + .appendSuper(super.hashCode()) + .append(rememberMe) + .toHashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final RememberMeUsernamePasswordCredential other = (RememberMeUsernamePasswordCredential) obj; + if (this.rememberMe != other.rememberMe) { + return false; + } + return true; + } + + public final void setRememberMe(final boolean rememberMe) { + this.rememberMe = rememberMe; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/RequiredHandlerAuthenticationPolicy.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/RequiredHandlerAuthenticationPolicy.java new file mode 100644 index 000000000000..022f2fe38006 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/RequiredHandlerAuthenticationPolicy.java @@ -0,0 +1,67 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import javax.validation.constraints.NotNull; + +/** + * Authentication security policy that is satisfied iff a specified authentication handler successfully authenticates + * at least one credential. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class RequiredHandlerAuthenticationPolicy implements AuthenticationPolicy { + + /** Authentication handler name that is required to satisfy policy. */ + @NotNull + private final String requiredHandlerName; + + /** Flag to try all credentials before policy is satisfied. */ + private boolean tryAll; + + /** + * Instantiates a new required handler authentication policy. + * + * @param requiredHandlerName the required handler name + */ + public RequiredHandlerAuthenticationPolicy(final String requiredHandlerName) { + this.requiredHandlerName = requiredHandlerName; + } + + /** + * Sets the flag to try all credentials before the policy is satisfied. + * This flag is disabled by default such that the policy is satisfied immediately upon the first + * credential that is successfully authenticated by the required handler. + * + * @param tryAll True to force all credentials to be authenticated, false otherwise. + */ + public void setTryAll(final boolean tryAll) { + this.tryAll = tryAll; + } + + @Override + public boolean isSatisfiedBy(final Authentication authn) { + boolean credsOk = true; + if (this.tryAll) { + credsOk = authn.getCredentials().size() == authn.getSuccesses().size() + authn.getFailures().size(); + } + return credsOk && authn.getSuccesses().containsKey(this.requiredHandlerName); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/RequiredHandlerAuthenticationPolicyFactory.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/RequiredHandlerAuthenticationPolicyFactory.java new file mode 100644 index 000000000000..881da84640a3 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/RequiredHandlerAuthenticationPolicyFactory.java @@ -0,0 +1,53 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.services.ServiceContext; + +/** + * Produces {@link ContextualAuthenticationPolicy} instances that are satisfied iff the given {@link Authentication} + * was created by authenticating credentials by all handlers named in + * {@link org.jasig.cas.services.RegisteredService#getRequiredHandlers()}. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class RequiredHandlerAuthenticationPolicyFactory implements ContextualAuthenticationPolicyFactory { + + @Override + public ContextualAuthenticationPolicy createPolicy(final ServiceContext context) { + return new ContextualAuthenticationPolicy() { + + @Override + public ServiceContext getContext() { + return context; + } + + @Override + public boolean isSatisfiedBy(final Authentication authentication) { + for (final String required : context.getRegisteredService().getRequiredHandlers()) { + if (!authentication.getSuccesses().containsKey(required)) { + return false; + } + } + return true; + } + }; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/SuccessfulHandlerMetaDataPopulator.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/SuccessfulHandlerMetaDataPopulator.java new file mode 100644 index 000000000000..e67a01b90dca --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/SuccessfulHandlerMetaDataPopulator.java @@ -0,0 +1,52 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import java.util.HashSet; +import java.util.Set; + +/** + * Sets an authentication attribute containing the collection of authentication handlers (by name) that successfully + * authenticated credential. The attribute name is given by {@link #SUCCESSFUL_AUTHENTICATION_HANDLERS}. + * This component provides a simple method to inject successful handlers into the CAS ticket validation + * response to support level of assurance and MFA use cases. + * + * @author Marvin S. Addison + * @author Alaa Nassef + * @since 4.0.0 + */ +public class SuccessfulHandlerMetaDataPopulator implements AuthenticationMetaDataPopulator { + /** Attribute name containing collection of handler names that successfully authenticated credential. */ + public static final String SUCCESSFUL_AUTHENTICATION_HANDLERS = "successfulAuthenticationHandlers"; + + @Override + public void populateAttributes(final AuthenticationBuilder builder, final Credential credential) { + Set successes = builder.getSuccesses().keySet(); + if (successes != null) { + successes = new HashSet(successes); + } + + builder.addAttribute(SUCCESSFUL_AUTHENTICATION_HANDLERS, successes); + } + + @Override + public boolean supports(final Credential credential) { + return true; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/UnresolvedPrincipalException.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/UnresolvedPrincipalException.java new file mode 100644 index 000000000000..d51cab3b4861 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/UnresolvedPrincipalException.java @@ -0,0 +1,54 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +/** + * Describes an error condition where a principal could not be resolved. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class UnresolvedPrincipalException extends PrincipalException { + + /** Serialization version marker. */ + private static final long serialVersionUID = 380456166081802820L; + + /** Error message when there was no error that prevent principal resolution. */ + private static final String UNRESOLVED_PRINCIPAL = "No resolver produced a principal."; + + /** + * Creates a new instance from an authentication event that was successful prior to principal resolution. + * + * @param authentication Authentication event. + */ + public UnresolvedPrincipalException(final Authentication authentication) { + super(UNRESOLVED_PRINCIPAL, authentication.getFailures(), authentication.getSuccesses()); + } + + /** + * Creates a new instance from an authentication event that was successful prior to principal resolution. + * This form should be used when a resolver exception prevented principal resolution. + * + * @param authentication Authentication event. + * @param cause Exception that prevented principal resolution. + */ + public UnresolvedPrincipalException(final Authentication authentication, final Exception cause) { + super(cause.getMessage(), authentication.getFailures(), authentication.getSuccesses()); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/UsernamePasswordCredential.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/UsernamePasswordCredential.java new file mode 100644 index 000000000000..f0a42fce084e --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/UsernamePasswordCredential.java @@ -0,0 +1,140 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.io.Serializable; + +/** + * Credential for authenticating with a username and password. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.0.0 + */ +public class UsernamePasswordCredential implements Credential, Serializable { + + /** Authentication attribute name for password. **/ + public static final String AUTHENTICATION_ATTRIBUTE_PASSWORD = "credential"; + + /** Unique ID for serialization. */ + private static final long serialVersionUID = -700605081472810939L; + + /** Password suffix appended to username in string representation. */ + private static final String PASSWORD_SUFFIX = "+password"; + + /** The username. */ + @NotNull + @Size(min=1, message = "required.username") + private String username; + + /** The password. */ + @NotNull + @Size(min=1, message = "required.password") + private String password; + + /** Default constructor. */ + public UsernamePasswordCredential() {} + + /** + * Creates a new instance with the given username and password. + * + * @param userName Non-null user name. + * @param password Non-null password. + */ + public UsernamePasswordCredential(final String userName, final String password) { + this.username = userName; + this.password = password; + } + + /** + * @return Returns the password. + */ + public final String getPassword() { + return this.password; + } + + /** + * @param password The password to set. + */ + public final void setPassword(final String password) { + this.password = password; + } + + /** + * @return Returns the userName. + */ + public final String getUsername() { + return this.username; + } + + /** + * @param userName The userName to set. + */ + public final void setUsername(final String userName) { + this.username = userName; + } + + /** + * {@inheritDoc} + */ + @Override + public String getId() { + return this.username; + } + + @Override + public String toString() { + return this.username + PASSWORD_SUFFIX; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final UsernamePasswordCredential that = (UsernamePasswordCredential) o; + + if (password != null ? !password.equals(that.password) : that.password != null) { + return false; + } + + if (username != null ? !username.equals(that.username) : that.username != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return new HashCodeBuilder() + .append(username) + .append(password) + .toHashCode(); + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/AuthenticationException.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/AuthenticationException.java new file mode 100644 index 000000000000..ddd4b8fe72c0 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/AuthenticationException.java @@ -0,0 +1,92 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +import org.jasig.cas.authentication.RootCasException; + +import javax.validation.constraints.NotNull; + +/** + * The most generic type of authentication exception that one can catch if not + * sure what specific implementation will be thrown. Top of the tree of all + * other AuthenticationExceptions. + * + * @author Scott Battaglia + * @since 3.0.0 + * @deprecated As of 4.1, the class is required to note its abstractness in the name and will be renamed in the future. + */ +@Deprecated +public abstract class AuthenticationException extends RootCasException { + + /** Serializable ID. */ + private static final long serialVersionUID = 3906648604830611762L; + + private static final String DEFAULT_TYPE = "error"; + + /** The error type that provides additional info about the nature of the exception cause. **/ + private final String type; + + /** + * Instantiates a new authentication exception. + * + * @param code the code + */ + public AuthenticationException(final String code) { + this(code, "", DEFAULT_TYPE); + } + + /** + * Instantiates a new authentication exception. + * + * @param code the code + * @param msg the msg + */ + public AuthenticationException(final String code, final String msg) { + this(code, msg, DEFAULT_TYPE); + } + + /** + * @param type The type of the error message that caused the exception to be thrown. By default, + * all errors are considered of error. + * @param code the exception code + * @param msg the error message + */ + public AuthenticationException(final String code, final String msg, @NotNull final String type) { + super(code, msg); + this.type = type; + } + + /** + * @param code the exception code + * @param throwable the exception that originally caused the authentication failure + */ + public AuthenticationException(final String code, final Throwable throwable) { + super(code, throwable); + this.type = DEFAULT_TYPE; + } + + /** + * Method to return the error type of this exception. + * + * @return the String identifier for the cause of this error. + */ + public final String getType() { + return this.type; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/BadCredentialsAuthenticationException.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/BadCredentialsAuthenticationException.java new file mode 100644 index 000000000000..15cef68b7aca --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/BadCredentialsAuthenticationException.java @@ -0,0 +1,86 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +/** + * Generic Bad Credential Exception. This can be thrown when the system knows + * the credentials are not valid specificially because they are bad. Subclasses + * can be specific to a certain type of Credential + * (BadUsernamePassowrdCredentials). + * + * @author Scott Battaglia + + * @since 3.0.0 + */ +public class BadCredentialsAuthenticationException extends AuthenticationException { + /** + * Default constructor that does not allow the chaining of exceptions and + * uses the default code as the error code for this exception. + */ + public static final String CODE = "error.authentication.credentials.bad"; + + /** + * Static instance of class to prevent cost incurred by creating new + * instance. + */ + public static final BadCredentialsAuthenticationException ERROR = new BadCredentialsAuthenticationException(); + + /** UID for serializable objects. */ + private static final long serialVersionUID = 3256719585087797044L; + + /** + * Default constructor that does not allow the chaining of exceptions and + * uses the default code as the error code for this exception. + */ + public BadCredentialsAuthenticationException() { + super(CODE); + } + + /** + * Constructor to allow for the chaining of exceptions. Constructor defaults + * to default code. + * + * @param throwable the chainable exception. + */ + public BadCredentialsAuthenticationException(final Throwable throwable) { + super(CODE, throwable); + } + + /** + * Constructor method to allow for providing a custom code to associate with + * this exception. + * + * @param code the code to use. + */ + public BadCredentialsAuthenticationException(final String code) { + super(code); + } + + /** + * Constructor to allow for the chaining of exceptions and use of a + * non-default code. + * + * @param code the user-specified code. + * @param throwable the chainable exception. + */ + public BadCredentialsAuthenticationException(final String code, + final Throwable throwable) { + super(code, throwable); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/BadPasswordAuthenticationException.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/BadPasswordAuthenticationException.java new file mode 100644 index 000000000000..a40d262491b7 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/BadPasswordAuthenticationException.java @@ -0,0 +1,81 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +/** + * The exception to throw when we know the username is correct but the password + * is not. + * + * @author Scott Battaglia + + * @since 3.0.0 + */ +public class BadPasswordAuthenticationException extends + BadUsernameOrPasswordAuthenticationException { + + /** Static instance of BadPasswordAuthenticationException. */ + public static final BadPasswordAuthenticationException ERROR = new BadPasswordAuthenticationException(); + + /** Unique ID for serializing. */ + private static final long serialVersionUID = 3977861752513837361L; + + /** The default code for this exception used for message resolving. */ + private static final String CODE = "error.authentication.credentials.bad.usernameorpassword.password"; + + /** + * Default constructor that does not allow the chaining of exceptions and + * uses the default code as the error code for this exception. + */ + public BadPasswordAuthenticationException() { + super(CODE); + } + + /** + * Constructor that allows for the chaining of exceptions. Defaults to the + * default code provided for this exception. + * + * @param throwable the chained exception. + */ + public BadPasswordAuthenticationException(final Throwable throwable) { + super(CODE, throwable); + } + + /** + * Constructor that allows for providing a custom error code for this class. + * Error codes are often used to resolve exceptions into messages. Providing + * a custom error code allows the use of a different message. + * + * @param code the custom code to use with this exception. + */ + public BadPasswordAuthenticationException(final String code) { + super(code); + } + + /** + * Constructor that allows for chaining of exceptions and a custom error + * code. + * + * @param code the custom error code to use in message resolving. + * @param throwable the chained exception. + */ + public BadPasswordAuthenticationException(final String code, + final Throwable throwable) { + super(code, throwable); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/BadUsernameOrPasswordAuthenticationException.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/BadUsernameOrPasswordAuthenticationException.java new file mode 100644 index 000000000000..3e1b2cc4d0ef --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/BadUsernameOrPasswordAuthenticationException.java @@ -0,0 +1,83 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +/** + * Exception to throw when we know the credentials provided were + * username/password and the combination is wrong. + * + * @author Scott Battaglia + + * @since 3.0.0 + */ +public class BadUsernameOrPasswordAuthenticationException extends + BadCredentialsAuthenticationException { + + /** Static instance of BadUsernameOrPasswordAuthenticationException. */ + public static final BadUsernameOrPasswordAuthenticationException ERROR = + new BadUsernameOrPasswordAuthenticationException(); + + /** Unique ID for serializing. */ + private static final long serialVersionUID = 3977861752513837361L; + + /** The default code for this exception used for message resolving. */ + private static final String CODE = "error.authentication.credentials.bad.usernameorpassword"; + + /** + * Default constructor that does not allow the chaining of exceptions and + * uses the default code as the error code for this exception. + */ + public BadUsernameOrPasswordAuthenticationException() { + super(CODE); + } + + /** + * Constructor that allows for the chaining of exceptions. Defaults to the + * default code provided for this exception. + * + * @param throwable the chained exception. + */ + public BadUsernameOrPasswordAuthenticationException( + final Throwable throwable) { + super(CODE, throwable); + } + + /** + * Constructor that allows for providing a custom error code for this class. + * Error codes are often used to resolve exceptions into messages. Providing + * a custom error code allows the use of a different message. + * + * @param code the custom code to use with this exception. + */ + public BadUsernameOrPasswordAuthenticationException(final String code) { + super(code); + } + + /** + * Constructor that allows for chaining of exceptions and a custom error + * code. + * + * @param code the custom error code to use in message resolving. + * @param throwable the chained exception. + */ + public BadUsernameOrPasswordAuthenticationException(final String code, + final Throwable throwable) { + super(code, throwable); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/BlockedCredentialsAuthenticationException.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/BlockedCredentialsAuthenticationException.java new file mode 100644 index 000000000000..7bde9f573bf2 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/BlockedCredentialsAuthenticationException.java @@ -0,0 +1,82 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +/** + * Exception to represent credentials that have been blocked for a reason such + * as Locked account. + * + * @author Scott Battaglia + + * @since 3.0.0 + */ +public class BlockedCredentialsAuthenticationException extends + AuthenticationException { + + /** Static instance of BlockedCredentialsAuthenticationException. */ + public static final BlockedCredentialsAuthenticationException ERROR = + new BlockedCredentialsAuthenticationException(); + + /** Unique ID for serialization. */ + private static final long serialVersionUID = 3544669598642420017L; + + /** The default code for this exception used for message resolving. */ + private static final String CODE = "error.authentication.credentials.blocked"; + + /** + * Default constructor that does not allow the chaining of exceptions and + * uses the default code as the error code for this exception. + */ + public BlockedCredentialsAuthenticationException() { + super(CODE); + } + + /** + * Constructor that allows for the chaining of exceptions. Defaults to the + * default code provided for this exception. + * + * @param throwable the chained exception. + */ + public BlockedCredentialsAuthenticationException(final Throwable throwable) { + super(CODE, throwable); + } + + /** + * Constructor that allows for providing a custom error code for this class. + * Error codes are often used to resolve exceptions into messages. Providing + * a custom error code allows the use of a different message. + * + * @param code the custom code to use with this exception. + */ + public BlockedCredentialsAuthenticationException(final String code) { + super(code); + } + + /** + * Constructor that allows for chaining of exceptions and a custom error + * code. + * + * @param code the custom error code to use in message resolving. + * @param throwable the chained exception. + */ + public BlockedCredentialsAuthenticationException(final String code, + final Throwable throwable) { + super(code, throwable); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/ConvertCasePrincipalNameTransformer.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/ConvertCasePrincipalNameTransformer.java new file mode 100644 index 000000000000..258695a229a3 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/ConvertCasePrincipalNameTransformer.java @@ -0,0 +1,66 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +import javax.validation.constraints.NotNull; + + +/** + * A transformer that converts the form uid to either lowercase or + * uppercase. The result is also trimmed. The transformer is also able + * to accept and work on the result of a previous transformer that might + * have modified the uid, such that the two can be chained. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class ConvertCasePrincipalNameTransformer implements PrincipalNameTransformer { + private boolean toUpperCase; + + @NotNull + private final PrincipalNameTransformer delegateTransformer; + + /** + * Instantiates a new transformer, while initializing the + * inner delegate to {@link NoOpPrincipalNameTransformer}. + */ + public ConvertCasePrincipalNameTransformer() { + this.delegateTransformer = new NoOpPrincipalNameTransformer(); + } + + /** + * Instantiates a new transformer, accepting an inner delegate. + * + * @param delegate the delegate + */ + public ConvertCasePrincipalNameTransformer(final PrincipalNameTransformer delegate) { + this.delegateTransformer = delegate; + } + + + @Override + public String transform(final String formUserId) { + final String result = this.delegateTransformer.transform(formUserId.trim()).trim(); + return this.toUpperCase ? result.toUpperCase(): result.toLowerCase(); + } + + public final void setToUpperCase(final boolean toUpperCase) { + this.toUpperCase = toUpperCase; + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/DefaultPasswordEncoder.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/DefaultPasswordEncoder.java new file mode 100644 index 000000000000..8719a8d38b78 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/DefaultPasswordEncoder.java @@ -0,0 +1,104 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +import org.springframework.util.StringUtils; + +import javax.validation.constraints.NotNull; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Implementation of PasswordEncoder using message digest. Can accept any + * message digest that the JDK can accept, including MD5 and SHA1. Returns the + * equivalent Hash you would get from a Perl digest. + * + * @author Scott Battaglia + * @author Stephen More + * @since 3.1 + */ +public final class DefaultPasswordEncoder implements PasswordEncoder { + + private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + private static final int HEX_RIGHT_SHIFT_COEFFICIENT = 4; + private static final int HEX_HIGH_BITS_BITWISE_FLAG = 0x0f; + + @NotNull + private final String encodingAlgorithm; + + private String characterEncoding; + + /** + * Instantiates a new default password encoder. + * + * @param encodingAlgorithm the encoding algorithm + */ + public DefaultPasswordEncoder(final String encodingAlgorithm) { + this.encodingAlgorithm = encodingAlgorithm; + } + + @Override + public String encode(final String password) { + if (password == null) { + return null; + } + + try { + final MessageDigest messageDigest = MessageDigest.getInstance(this.encodingAlgorithm); + + if (StringUtils.hasText(this.characterEncoding)) { + messageDigest.update(password.getBytes(this.characterEncoding)); + } else { + messageDigest.update(password.getBytes(Charset.defaultCharset())); + } + + + final byte[] digest = messageDigest.digest(); + + return getFormattedText(digest); + } catch (final NoSuchAlgorithmException e) { + throw new SecurityException(e); + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + /** + * Takes the raw bytes from the digest and formats them correct. + * + * @param bytes the raw bytes from the digest. + * @return the formatted bytes. + */ + private String getFormattedText(final byte[] bytes) { + final StringBuilder buf = new StringBuilder(bytes.length * 2); + + for (int j = 0; j < bytes.length; j++) { + buf.append(HEX_DIGITS[(bytes[j] >> HEX_RIGHT_SHIFT_COEFFICIENT) & HEX_HIGH_BITS_BITWISE_FLAG]); + buf.append(HEX_DIGITS[bytes[j] & HEX_HIGH_BITS_BITWISE_FLAG]); + } + return buf.toString(); + } + + public void setCharacterEncoding(final String characterEncoding) { + this.characterEncoding = characterEncoding; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/NoOpPrincipalNameTransformer.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/NoOpPrincipalNameTransformer.java new file mode 100644 index 000000000000..95bad8ae0eaa --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/NoOpPrincipalNameTransformer.java @@ -0,0 +1,34 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +/** + * Simple implementation that actually does NO transformation. + * + * @author Scott Battaglia + + * @since 3.3.6 + */ +public final class NoOpPrincipalNameTransformer implements PrincipalNameTransformer { + + @Override + public String transform(final String formUserId) { + return formUserId; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/PlainTextPasswordEncoder.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/PlainTextPasswordEncoder.java new file mode 100644 index 000000000000..3110bc63b39c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/PlainTextPasswordEncoder.java @@ -0,0 +1,35 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +/** + * Default password encoder for the case where no password encoder is needed. + * Encoding results in the same password that was passed in. + * + * @author Scott Battaglia + + * @since 3.0.0 + */ +public final class PlainTextPasswordEncoder implements PasswordEncoder { + + @Override + public String encode(final String password) { + return password; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/PrefixSuffixPrincipalNameTransformer.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/PrefixSuffixPrincipalNameTransformer.java new file mode 100644 index 000000000000..c701b2a9ec73 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/PrefixSuffixPrincipalNameTransformer.java @@ -0,0 +1,79 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +/** + * Transform the user id by adding a prefix or suffix. + * + * @author Howard Gilbert + * @author Scott Battaglia + + * @since 3.3.6 + */ + +public final class PrefixSuffixPrincipalNameTransformer implements PrincipalNameTransformer { + + private String prefix; + + private String suffix; + + /** + * Instantiates a new Prefix suffix principal name transformer. + */ + public PrefixSuffixPrincipalNameTransformer() { + this.prefix = null; + this.suffix = null; + } + + /** + * Instantiates a new Prefix suffix principal name transformer. + * + * @param prefix the prefix + * @param suffix the suffix + */ + public PrefixSuffixPrincipalNameTransformer(final String prefix, final String suffix) { + setPrefix(prefix); + setSuffix(suffix); + } + + @Override + public String transform(final String formUserId) { + final StringBuilder stringBuilder = new StringBuilder(); + + if (this.prefix != null) { + stringBuilder.append(this.prefix); + } + + stringBuilder.append(formUserId); + + if (this.suffix != null) { + stringBuilder.append(this.suffix); + } + + return stringBuilder.toString(); + } + + public void setPrefix(final String prefix) { + this.prefix = prefix; + } + + public void setSuffix(final String suffix) { + this.suffix = suffix; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/UncategorizedAuthenticationException.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/UncategorizedAuthenticationException.java new file mode 100644 index 000000000000..c39cf5885a7e --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/UncategorizedAuthenticationException.java @@ -0,0 +1,56 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +/** + * Generic abstract exception to extend when you don't know what the heck is + * going on. + * + * @author Scott Battaglia + * @since 3.0.0 + * @deprecated As of 4.1, the class is required to note its abstractness in the name and will be renamed in the future. + */ +@Deprecated +public abstract class UncategorizedAuthenticationException extends AuthenticationException { + + private static final long serialVersionUID = 872764495107139229L; + + /** + * Constructor that allows for providing a custom error code for this class. + * Error codes are often used to resolve exceptions into messages. Providing + * a custom error code allows the use of a different message. + * + * @param code the custom code to use with this exception. + */ + public UncategorizedAuthenticationException(final String code) { + super(code); + } + + /** + * Constructor that allows for chaining of exceptions and a custom error + * code. + * + * @param code the custom error code to use in message resolving. + * @param throwable the chained exception. + */ + public UncategorizedAuthenticationException(final String code, + final Throwable throwable) { + super(code, throwable); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/UnknownUsernameAuthenticationException.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/UnknownUsernameAuthenticationException.java new file mode 100644 index 000000000000..07d8950b374e --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/UnknownUsernameAuthenticationException.java @@ -0,0 +1,81 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +/** + * The exception to throw when we explicitly don't know anything about the + * username. + * + * @author Scott Battaglia + + * @since 3.0.0 + */ +public class UnknownUsernameAuthenticationException extends + BadUsernameOrPasswordAuthenticationException { + + /** Static instance of UnknownUsernameAuthenticationException. */ + public static final UnknownUsernameAuthenticationException ERROR = new UnknownUsernameAuthenticationException(); + + /** Unique ID for serializing. */ + private static final long serialVersionUID = 3977861752513837361L; + + /** The code description of this exception. */ + private static final String CODE = "error.authentication.credentials.bad.usernameorpassword.username"; + + /** + * Default constructor that does not allow the chaining of exceptions and + * uses the default code as the error code for this exception. + */ + public UnknownUsernameAuthenticationException() { + super(CODE); + } + + /** + * Constructor that allows for the chaining of exceptions. Defaults to the + * default code provided for this exception. + * + * @param throwable the chained exception. + */ + public UnknownUsernameAuthenticationException(final Throwable throwable) { + super(CODE, throwable); + } + + /** + * Constructor that allows for providing a custom error code for this class. + * Error codes are often used to resolve exceptions into messages. Providing + * a custom error code allows the use of a different message. + * + * @param code the custom code to use with this exception. + */ + public UnknownUsernameAuthenticationException(final String code) { + super(code); + } + + /** + * Constructor that allows for chaining of exceptions and a custom error + * code. + * + * @param code the custom error code to use in message resolving. + * @param throwable the chained exception. + */ + public UnknownUsernameAuthenticationException(final String code, + final Throwable throwable) { + super(code, throwable); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/UnsupportedCredentialsException.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/UnsupportedCredentialsException.java new file mode 100644 index 000000000000..d3ccdb6f9cfb --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/UnsupportedCredentialsException.java @@ -0,0 +1,59 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +/** + * The exception thrown when a Handler does not know how to determine the + * validity of the credentials based on the fact that it does not know what to + * do with the credentials presented. + * + * @author Scott Battaglia + + * @since 3.0.0 + */ +public final class UnsupportedCredentialsException extends + AuthenticationException { + + /** Static instance of UnsupportedCredentialsException. */ + public static final UnsupportedCredentialsException ERROR = new UnsupportedCredentialsException(); + + /** Unique ID for serializing. */ + private static final long serialVersionUID = 3977861752513837361L; + + /** The code description of this exception. */ + private static final String CODE = "error.authentication.credentials.unsupported"; + + /** + * Default constructor that does not allow the chaining of exceptions and + * uses the default code as the error code for this exception. + */ + public UnsupportedCredentialsException() { + super(CODE); + } + + /** + * Constructor that allows for the chaining of exceptions. Defaults to the + * default code provided for this exception. + * + * @param throwable the chained exception. + */ + public UnsupportedCredentialsException(final Throwable throwable) { + super(CODE, throwable); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/package.html b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/package.html new file mode 100644 index 000000000000..2dbb8c5d460e --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/package.html @@ -0,0 +1,46 @@ + + + +The handler package contains the classes used to authenticate a user. It contains +the AuthenticationHandler interface which is used to validate credential. It also +contains the PasswordEncoders which are used by implementations of the AuthenticationHandler +to provide conversion from plain text to whatever the password is encoded as in the data +store. +

+The package also contains a well-defined exception heirarchy to allow fine-grained error +messages to be displayed. +

+Examples of AuthenticationHandlers implementations: +

    +
  • If the credential are a Userid and Password, then it submits them to an +external Kerberos, LDAP, or JDBC authority for validation.
  • +
  • If the credential are a Certificate, then it verifies the Issuer chain +against some list of reliable CAs, checks the date to make sure it hasn't +expired, and checks the CRL to make sure it wasn't revoked.
  • +
  • If authentication has been done by the Servlet Container or by a Filter, then +the Credentials have been extracted from the HttpRequest object. Notably, this +will include the REMOTE_USER. Such Credentials are implicitly trusted and self +validating, so an AuthenticationHandler recognizing such an object will indicate +that it is valid without inspecting its contents.
  • +
+ + diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/AbstractPreAndPostProcessingAuthenticationHandler.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/AbstractPreAndPostProcessingAuthenticationHandler.java new file mode 100644 index 000000000000..5b256cd785d5 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/AbstractPreAndPostProcessingAuthenticationHandler.java @@ -0,0 +1,117 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler.support; + +import org.jasig.cas.MessageDescriptor; +import org.jasig.cas.authentication.AbstractAuthenticationHandler; +import org.jasig.cas.authentication.BasicCredentialMetaData; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.principal.Principal; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.security.auth.login.FailedLoginException; +import java.security.GeneralSecurityException; +import java.util.List; + +/** + * Abstract authentication handler that allows deployers to utilize the bundled + * AuthenticationHandlers while providing a mechanism to perform tasks before + * and after authentication. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * + * @since 3.1 + */ +public abstract class AbstractPreAndPostProcessingAuthenticationHandler extends AbstractAuthenticationHandler { + + /** Instance of logging for subclasses. */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * Template method to perform arbitrary pre-authentication actions. + * + * @param credential the Credential supplied + * @return true if authentication should continue, false otherwise. + */ + protected boolean preAuthenticate(final Credential credential) { + return true; + } + + /** + * Template method to perform arbitrary post-authentication actions. + * + * @param credential the supplied credential + * @param result the result of the authentication attempt. + * + * @return An authentication handler result that MAY be different or modified from that provided. + */ + protected HandlerResult postAuthenticate(final Credential credential, final HandlerResult result) { + return result; + } + + /** + * {@inheritDoc} + **/ + @Override + public final HandlerResult authenticate(final Credential credential) + throws GeneralSecurityException, PreventedException { + + if (!preAuthenticate(credential)) { + throw new FailedLoginException(); + } + + return postAuthenticate(credential, doAuthentication(credential)); + } + + /** + * Performs the details of authentication and returns an authentication handler result on success. + * + * + * @param credential Credential to authenticate. + * + * @return Authentication handler result on success. + * + * @throws GeneralSecurityException On authentication failure that is thrown out to the caller of + * {@link #authenticate(org.jasig.cas.authentication.Credential)}. + * @throws PreventedException On the indeterminate case when authentication is prevented. + */ + protected abstract HandlerResult doAuthentication(final Credential credential) + throws GeneralSecurityException, PreventedException; + + /** + * Helper method to construct a handler result + * on successful authentication events. + * + * @param credential the credential on which the authentication was successfully performed. + * Note that this credential instance may be different from what was originally provided + * as transformation of the username may have occurred, if one is in fact defined. + * @param principal the resolved principal + * @param warnings the warnings + * @return the constructed handler result + */ + protected final HandlerResult createHandlerResult(final Credential credential, final Principal principal, + final List warnings) { + return new DefaultHandlerResult(this, new BasicCredentialMetaData(credential), principal, warnings); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/AbstractUsernamePasswordAuthenticationHandler.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/AbstractUsernamePasswordAuthenticationHandler.java new file mode 100644 index 000000000000..55c62c723d02 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/AbstractUsernamePasswordAuthenticationHandler.java @@ -0,0 +1,136 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler.support; + +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.handler.NoOpPrincipalNameTransformer; +import org.jasig.cas.authentication.handler.PasswordEncoder; +import org.jasig.cas.authentication.handler.PlainTextPasswordEncoder; +import org.jasig.cas.authentication.handler.PrincipalNameTransformer; +import org.jasig.cas.authentication.support.PasswordPolicyConfiguration; + +import javax.security.auth.login.AccountNotFoundException; +import javax.validation.constraints.NotNull; +import java.security.GeneralSecurityException; + +/** + * Abstract class to override supports so that we don't need to duplicate the + * check for UsernamePasswordCredential. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * + * @since 3.0.0 + */ +public abstract class AbstractUsernamePasswordAuthenticationHandler extends + AbstractPreAndPostProcessingAuthenticationHandler { + + /** + * PasswordEncoder to be used by subclasses to encode passwords for + * comparing against a resource. + */ + @NotNull + private PasswordEncoder passwordEncoder = new PlainTextPasswordEncoder(); + + @NotNull + private PrincipalNameTransformer principalNameTransformer = new NoOpPrincipalNameTransformer(); + + /** The password policy configuration to be used by extensions. */ + private PasswordPolicyConfiguration passwordPolicyConfiguration; + + /** + * {@inheritDoc} + **/ + @Override + protected final HandlerResult doAuthentication(final Credential credential) + throws GeneralSecurityException, PreventedException { + final UsernamePasswordCredential userPass = (UsernamePasswordCredential) credential; + if (userPass.getUsername() == null) { + throw new AccountNotFoundException("Username is null."); + } + + final String transformedUsername= this.principalNameTransformer.transform(userPass.getUsername()); + if (transformedUsername == null) { + throw new AccountNotFoundException("Transformed username is null."); + } + userPass.setUsername(transformedUsername); + return authenticateUsernamePasswordInternal(userPass); + } + + /** + * Authenticates a username/password credential by an arbitrary strategy. + * + * @param transformedCredential the credential object bearing the transformed username and password. + * + * @return HandlerResult resolved from credential on authentication success or null if no principal could be resolved + * from the credential. + * + * @throws GeneralSecurityException On authentication failure. + * @throws PreventedException On the indeterminate case when authentication is prevented. + */ + protected abstract HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential transformedCredential) + throws GeneralSecurityException, PreventedException; + + /** + * Method to return the PasswordEncoder to be used to encode passwords. + * + * @return the PasswordEncoder associated with this class. + */ + protected final PasswordEncoder getPasswordEncoder() { + return this.passwordEncoder; + } + + protected final PrincipalNameTransformer getPrincipalNameTransformer() { + return this.principalNameTransformer; + } + + protected final PasswordPolicyConfiguration getPasswordPolicyConfiguration() { + return this.passwordPolicyConfiguration; + } + + /** + * Sets the PasswordEncoder to be used with this class. + * + * @param passwordEncoder the PasswordEncoder to use when encoding + * passwords. + */ + public final void setPasswordEncoder(final PasswordEncoder passwordEncoder) { + this.passwordEncoder = passwordEncoder; + } + + public final void setPrincipalNameTransformer(final PrincipalNameTransformer principalNameTransformer) { + this.principalNameTransformer = principalNameTransformer; + } + + public final void setPasswordPolicyConfiguration(final PasswordPolicyConfiguration passwordPolicyConfiguration) { + this.passwordPolicyConfiguration = passwordPolicyConfiguration; + } + + /** + * {@inheritDoc} + * @return True if credential is a {@link UsernamePasswordCredential}, false otherwise. + */ + @Override + public boolean supports(final Credential credential) { + return credential instanceof UsernamePasswordCredential; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/HttpBasedServiceCredentialsAuthenticationHandler.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/HttpBasedServiceCredentialsAuthenticationHandler.java new file mode 100644 index 000000000000..465c75e409a1 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/HttpBasedServiceCredentialsAuthenticationHandler.java @@ -0,0 +1,104 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler.support; + +import java.net.URL; +import java.security.GeneralSecurityException; + +import javax.security.auth.login.FailedLoginException; +import javax.validation.constraints.NotNull; + +import org.jasig.cas.authentication.AbstractAuthenticationHandler; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.HttpBasedServiceCredential; +import org.jasig.cas.util.http.HttpClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class to validate the credential presented by communicating with the web + * server and checking the certificate that is returned against the hostname, + * etc. + *

+ * This class is concerned with ensuring that the protocol is HTTPS and that a + * response is returned. The SSL handshake that occurs automatically by opening + * a connection does the heavy process of authenticating. + * + * @author Scott Battaglia + + * @since 3.0.0 + */ +public final class HttpBasedServiceCredentialsAuthenticationHandler extends AbstractAuthenticationHandler { + + /** Log instance. */ + private final Logger logger = LoggerFactory.getLogger(getClass()); + + /** Instance of Apache Commons HttpClient. */ + @NotNull + private HttpClient httpClient; + + @Override + public HandlerResult authenticate(final Credential credential) throws GeneralSecurityException { + final HttpBasedServiceCredential httpCredential = (HttpBasedServiceCredential) credential; + if (!httpCredential.getService().getProxyPolicy().isAllowedProxyCallbackUrl(httpCredential.getCallbackUrl())) { + logger.warn("Proxy policy for service [{}] cannot authorize the requested callbackurl [{}]", + httpCredential.getService(), httpCredential.getCallbackUrl()); + throw new FailedLoginException(httpCredential.getCallbackUrl() + " cannot be authorized"); + } + + logger.debug("Attempting to authenticate {}", httpCredential); + final URL callbackUrl = httpCredential.getCallbackUrl(); + if (!this.httpClient.isValidEndPoint(callbackUrl)) { + throw new FailedLoginException(callbackUrl.toExternalForm() + " sent an unacceptable response status code"); + } + return new DefaultHandlerResult(this, httpCredential, this.principalFactory.createPrincipal(httpCredential.getId())); + } + + /** + * {@inheritDoc} + * @return true if the credential provided are not null and the credential + * are a subclass of (or equal to) HttpBasedServiceCredential. + */ + @Override + public boolean supports(final Credential credential) { + return credential instanceof HttpBasedServiceCredential; + } + + /** + * Sets the HttpClient which will do all of the connection stuff. + * @param httpClient http client instance to use + **/ + public void setHttpClient(final HttpClient httpClient) { + this.httpClient = httpClient; + } + + /** + * @deprecated As of 4.1. Endpoint security is handled by service proxy policies + * + *

Set whether a secure url is required or not.

+ * + * @param requireSecure true if its required, false if not. Default is true. + */ + @Deprecated + public void setRequireSecure(final boolean requireSecure) { + logger.warn("setRequireSecure() is deprecated and will be removed. Callback url validation is controlled by the proxy policy"); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/JaasAuthenticationHandler.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/JaasAuthenticationHandler.java new file mode 100644 index 000000000000..5cb1d171c6d9 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/JaasAuthenticationHandler.java @@ -0,0 +1,233 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler.support; + +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.principal.Principal; +import org.springframework.util.Assert; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.Configuration; +import javax.security.auth.login.LoginContext; +import javax.validation.constraints.NotNull; +import java.security.GeneralSecurityException; +import java.util.Set; + +/** + * JAAS Authentication Handler for CAAS. This is a simple bridge from CAS' + * authentication to JAAS. + * + *

+ * Using the JAAS Authentication Handler requires you to configure the + * appropriate JAAS modules. You can specify the location of a jass.conf file + * using the following VM parameter: + *

+ * -Djava.security.auth.login.config=$PATH_TO_JAAS_CONF/jaas.conf
+ * 
+ * + *

+ * This example jaas.conf would try Kerberos based authentication, then try LDAP + * authentication: + *

+ * CAS {
+ *   com.sun.security.auth.module.Krb5LoginModule sufficient
+ *     client=TRUE
+ *     debug=FALSE
+ *     useTicketCache=FALSE;
+ *   edu.uconn.netid.jaas.LDAPLoginModule sufficient
+ *     java.naming.provider.url="ldap://ldapserver.my.edu:389/dc=my,dc=edu"
+ *     java.naming.security.principal="uid=jaasauth,dc=my,dc=edu"
+ *     java.naming.security.credentials="password"
+ *     Attribute="uid"
+ *     startTLS="true";
+ * };
+ * 
+ * + * @author Matthew J. Smith + * @author Marvin S. Addison + * @author Misagh Moayyed + * + * @see javax.security.auth.callback.CallbackHandler + * @see javax.security.auth.callback.PasswordCallback + * @see javax.security.auth.callback.NameCallback + * @since 3.0.0.5 + */ +public class JaasAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler { + + /** If no realm is specified, we default to CAS. */ + private static final String DEFAULT_REALM = "CAS"; + + /** + * System property key to specify kerb5 realm. + */ + private static final String SYS_PROP_KRB5_REALM = "java.security.krb5.realm"; + + /** + * System property key to specify kerb5 kdc. + */ + private static final String SYS_PROP_KERB5_KDC = "java.security.krb5.kdc"; + + /** The realm that contains the login module information. */ + @NotNull + private String realm = DEFAULT_REALM; + + /** System property value to overwrite the realm in krb5 config. */ + private String kerberosRealmSystemProperty; + + /** System property value to overwrite the kdc in krb5 config. */ + private String kerberosKdcSystemProperty; + + /** + * Instantiates a new Jaas authentication handler, + * and attempts to load/verify the configuration. + */ + public JaasAuthenticationHandler() { + Assert.notNull(Configuration.getConfiguration(), + "Static Configuration cannot be null. Did you remember to specify \"java.security.auth.login.config\"?"); + } + + /** + * {@inheritDoc} + */ + @Override + protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential) + throws GeneralSecurityException, PreventedException { + + if (this.kerberosKdcSystemProperty != null) { + logger.debug("Setting kerberos system property {} to {}", SYS_PROP_KERB5_KDC, this.kerberosKdcSystemProperty); + System.setProperty(SYS_PROP_KERB5_KDC, this.kerberosKdcSystemProperty); + } + if (this.kerberosRealmSystemProperty != null) { + logger.debug("Setting kerberos system property {} to {}", SYS_PROP_KRB5_REALM, this.kerberosRealmSystemProperty); + System.setProperty(SYS_PROP_KRB5_REALM, this.kerberosRealmSystemProperty); + } + + final String username = credential.getUsername(); + final String password = getPasswordEncoder().encode(credential.getPassword()); + final LoginContext lc = new LoginContext( + this.realm, + new UsernamePasswordCallbackHandler(username, password)); + try { + logger.debug("Attempting authentication for: {}", username); + lc.login(); + } finally { + lc.logout(); + } + + Principal principal = null; + final Set principals = lc.getSubject().getPrincipals(); + if (principals != null && principals.size() > 0) { + principal = this.principalFactory.createPrincipal(principals.iterator().next().getName()); + } + return createHandlerResult(credential, principal, null); + } + + public void setRealm(final String realm) { + this.realm = realm; + } + + /** + * Typically, the default realm and the KDC for that realm are indicated in the Kerberos krb5.conf configuration file. + * However, if you like, you can instead specify the realm value by setting this following system property value. + *

If you set the realm property, you SHOULD also configure the {@link #setKerberosKdcSystemProperty(String)}. + *

Also note that if you set these properties, then no cross-realm authentication is possible unless + * a krb5.conf file is also provided from which the additional information required for cross-realm authentication + * may be obtained. + *

If you set values for these properties, then they override the default realm and KDC values specified + * in krb5.conf (if such a file is found). The krb5.conf file is still consulted if values for items + * other than the default realm and KDC are needed. If no krb5.conf file is found, + * then the default values used for these items are implementation-specific. + * @param kerberosRealmSystemProperty system property to indicate realm. + * @see + * Oracle documentation + * @since 4.1.0 + */ + public final void setKerberosRealmSystemProperty(final String kerberosRealmSystemProperty) { + this.kerberosRealmSystemProperty = kerberosRealmSystemProperty; + } + + /** + * Typically, the default realm and the KDC for that realm are indicated in the Kerberos krb5.conf configuration file. + * However, if you like, you can instead specify the kdc value by setting this system property value. + *

If you set the realm property, you SHOULD also configure the {@link #setKerberosRealmSystemProperty(String)}. + *

Also note that if you set these properties, then no cross-realm authentication is possible unless + * a krb5.conf file is also provided from which the additional information required for cross-realm authentication + * may be obtained. + *

If you set values for these properties, then they override the default realm and KDC values specified + * in krb5.conf (if such a file is found). The krb5.conf file is still consulted if values for items + * other than the default realm and KDC are needed. If no krb5.conf file is found, + * then the default values used for these items are implementation-specific. + * @param kerberosKdcSystemProperty system property to indicate kdc + * @see + * Oracle documentation + * @since 4.1.0 + */ + public final void setKerberosKdcSystemProperty(final String kerberosKdcSystemProperty) { + this.kerberosKdcSystemProperty = kerberosKdcSystemProperty; + } + + /** + * A simple JAAS CallbackHandler which accepts a Name String and Password + * String in the constructor. Only NameCallbacks and PasswordCallbacks are + * accepted in the callback array. This code based loosely on example given + * in Sun's javadoc for CallbackHandler interface. + */ + protected static final class UsernamePasswordCallbackHandler implements CallbackHandler { + + /** The username of the principal we are trying to authenticate. */ + private final String userName; + + /** The password of the principal we are trying to authenticate. */ + private final String password; + + /** + * Constructor accepts name and password to be used for authentication. + * + * @param userName name to be used for authentication + * @param password Password to be used for authentication + */ + protected UsernamePasswordCallbackHandler(final String userName, + final String password) { + this.userName = userName; + this.password = password; + } + + @Override + public void handle(final Callback[] callbacks) + throws UnsupportedCallbackException { + for (final Callback callback : callbacks) { + if (callback.getClass().equals(NameCallback.class)) { + ((NameCallback) callback).setName(this.userName); + } else if (callback.getClass().equals(PasswordCallback.class)) { + ((PasswordCallback) callback).setPassword(this.password + .toCharArray()); + } else { + throw new UnsupportedCallbackException(callback, + "Unrecognized Callback"); + } + } + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/package.html b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/package.html new file mode 100644 index 000000000000..3ad096e3744f --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/package.html @@ -0,0 +1,32 @@ + + + +

+Authentication.support contains the specific implementations of +the AuthenticationHandler interface. These implementations are designed +to authenticate a specific type of Credential. +

+AuthenticationHandlers are normally associated with the provided +AuthenticationManagerImpl which handles all aspects of the request for +Authentication. + + diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/package.html b/cas-server-core/src/main/java/org/jasig/cas/authentication/package.html new file mode 100644 index 000000000000..69dfe3bffe6e --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/package.html @@ -0,0 +1,39 @@ + + + + +

Authentication validates the Credentials provided during a /login +request. In this context, "Credentials" are an opaque object declared +with the Credentials marker interface. The AuthenticationManager +typically passes the Credentials to a sequence of plug-in elements +to see if any of them can recognize and process the concrete implementing +type.

+ +

Successful authentication generates a Principal object wrapped in an +Authentication object. All these objects must be serializable, and the +Authentication becomes part of the TGT in the ticket cache.

+ +

Unsucessful authentication must throw an AuthenticationException. The +AuthenticationManager may not return null to signal a failure.

+ + + diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/AbstractWebApplicationService.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/AbstractWebApplicationService.java new file mode 100644 index 000000000000..9ade26c34e0d --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/AbstractWebApplicationService.java @@ -0,0 +1,186 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URLDecoder; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Abstract implementation of a WebApplicationService. + * + * @author Scott Battaglia + * @since 3.1 + */ +public abstract class AbstractWebApplicationService implements SingleLogoutService { + + private static final long serialVersionUID = 610105280927740076L; + + private static final Map EMPTY_MAP = Collections.unmodifiableMap(new HashMap()); + + /** Logger instance. **/ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** The id of the service. */ + private final String id; + + /** The original url provided, used to reconstruct the redirect url. */ + private final String originalUrl; + + private final String artifactId; + + private Principal principal; + + private boolean loggedOutAlready; + + /** + * Instantiates a new abstract web application service. + * + * @param id the id + * @param originalUrl the original url + * @param artifactId the artifact id + */ + protected AbstractWebApplicationService(final String id, final String originalUrl, + final String artifactId) { + this.id = id; + this.originalUrl = originalUrl; + this.artifactId = artifactId; + } + + @Override + public final String toString() { + return this.id; + } + + public final String getId() { + return this.id; + } + + public final String getArtifactId() { + return this.artifactId; + } + + public final Map getAttributes() { + return EMPTY_MAP; + } + + /** + * Cleanup the url. Removes jsession ids and query strings. + * + * @param url the url + * @return sanitized url. + */ + protected static String cleanupUrl(final String url) { + if (url == null) { + return null; + } + + final int jsessionPosition = url.indexOf(";jsession"); + + if (jsessionPosition == -1) { + return url; + } + + final int questionMarkPosition = url.indexOf('?'); + + if (questionMarkPosition < jsessionPosition) { + return url.substring(0, url.indexOf(";jsession")); + } + + return url.substring(0, jsessionPosition) + + url.substring(questionMarkPosition); + } + + /** + * Return the original url provided (as service or targetService request parameter). + * Used to reconstruct the redirect url. + * + * @return the original url provided. + */ + public final String getOriginalUrl() { + return this.originalUrl; + } + + @Override + public boolean equals(final Object object) { + if (object == null) { + return false; + } + + if (object instanceof Service) { + final Service service = (Service) object; + + return getId().equals(service.getId()); + } + + return false; + } + + @Override + public int hashCode() { + return new HashCodeBuilder() + .append(this.id) + .toHashCode(); + } + + protected Principal getPrincipal() { + return this.principal; + } + + public void setPrincipal(final Principal principal) { + this.principal = principal; + } + + @Override + public boolean matches(final Service service) { + try { + final String thisUrl = URLDecoder.decode(this.id, "UTF-8"); + final String serviceUrl = URLDecoder.decode(service.getId(), "UTF-8"); + + logger.trace("Decoded urls and comparing [{}] with [{}]", thisUrl, serviceUrl); + return thisUrl.equalsIgnoreCase(serviceUrl); + } catch (final Exception e) { + logger.error(e.getMessage(), e); + } + return false; + } + + /** + * Return if the service is already logged out. + * + * @return if the service is already logged out. + */ + public boolean isLoggedOutAlready() { + return loggedOutAlready; + } + + /** + * Set if the service is already logged out. + * + * @param loggedOutAlready if the service is already logged out. + */ + public final void setLoggedOutAlready(final boolean loggedOutAlready) { + this.loggedOutAlready = loggedOutAlready; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/BasicPrincipalResolver.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/BasicPrincipalResolver.java new file mode 100644 index 000000000000..3878866bbcc2 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/BasicPrincipalResolver.java @@ -0,0 +1,57 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import org.jasig.cas.authentication.Credential; + +import javax.validation.constraints.NotNull; + +/** + * Provides the most basic means of principal resolution by mapping + * {@link org.jasig.cas.authentication.Credential#getId()} onto + * {@link org.jasig.cas.authentication.principal.Principal#getId()}. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class BasicPrincipalResolver implements PrincipalResolver { + + /** Factory to create the principal type. **/ + @NotNull + private PrincipalFactory principalFactory = new DefaultPrincipalFactory(); + + @Override + public Principal resolve(final Credential credential) { + return this.principalFactory.createPrincipal(credential.getId()); + } + + @Override + public boolean supports(final Credential credential) { + return credential.getId() != null; + } + + /** + * Sets principal factory to create principal objects. + * + * @param principalFactory the principal factory + */ + public void setPrincipalFactory(final PrincipalFactory principalFactory) { + this.principalFactory = principalFactory; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/CachingPrincipalAttributesRepository.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/CachingPrincipalAttributesRepository.java new file mode 100644 index 000000000000..f4be6a5e4f9d --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/CachingPrincipalAttributesRepository.java @@ -0,0 +1,335 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.jasig.services.persondir.IPersonAttributeDao; +import org.jasig.services.persondir.IPersonAttributes; +import org.jasig.services.persondir.support.merger.IAttributeMerger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.cache.Cache; +import javax.cache.CacheManager; +import javax.cache.Caching; +import javax.cache.configuration.MutableConfiguration; +import javax.cache.expiry.CreatedExpiryPolicy; +import javax.cache.expiry.Duration; +import javax.validation.constraints.NotNull; +import java.io.Closeable; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * Wrapper around an attribute repository where attributes cached for a configurable period. + * @author Misagh Moayyed + * @since 4.1 + */ +public final class CachingPrincipalAttributesRepository implements PrincipalAttributesRepository, Closeable { + private static final long serialVersionUID = 6350244643948535906L; + + private static final TimeUnit DEFAULT_CACHE_EXPIRATION_UNIT = TimeUnit.HOURS; + private static final long DEFAULT_CACHE_EXPIRATION_DURATION = 2; + + private static final Logger LOGGER = LoggerFactory.getLogger(CachingPrincipalAttributesRepository.class); + + private final IPersonAttributeDao attributeRepository; + + private final Cache> cache; + + private final String cacheName = this.getClass().getSimpleName().concat(UUID.randomUUID().toString()); + + /** + * The merging strategy that deals with existing principal attributes + * and those that are retrieved from the source. By default, existing attributes + * are ignored and the source is always consulted. + */ + private IAttributeMerger mergingStrategy; + + /** + * Init the caching repository, solely used for serialization purposes + * and nothing else. + */ + private CachingPrincipalAttributesRepository() { + this.attributeRepository = null; + this.cache = null; + } + + /** + * Instantiates a new caching attributes principal factory. + * Caches the attributes based on duration units of {@link #DEFAULT_CACHE_EXPIRATION_DURATION} + * and {@link #DEFAULT_CACHE_EXPIRATION_UNIT}. + * @param attributeRepository the attribute repository + */ + public CachingPrincipalAttributesRepository(final IPersonAttributeDao attributeRepository) { + this(attributeRepository, createCacheConfiguration( + new Duration(DEFAULT_CACHE_EXPIRATION_UNIT, DEFAULT_CACHE_EXPIRATION_DURATION))); + + } + + /** + * Instantiates a new caching attributes principal factory. + * Caches the attributes based on duration units of {@link #DEFAULT_CACHE_EXPIRATION_DURATION} + * and the given time. + * @param attributeRepository the attribute repository + * @param expiryDuration the expiry duration based on the unit of {@link #DEFAULT_CACHE_EXPIRATION_DURATION} + */ + public CachingPrincipalAttributesRepository(final IPersonAttributeDao attributeRepository, final long expiryDuration) { + this(attributeRepository, createCacheConfiguration( + new Duration(DEFAULT_CACHE_EXPIRATION_UNIT, expiryDuration))); + + } + + /** + * Instantiates a new caching attributes principal factory. + * + * @param attributeRepository the attribute repository + * @param timeUnit the time unit + * @param expiryDuration the expiry duration + */ + public CachingPrincipalAttributesRepository(final IPersonAttributeDao attributeRepository, + final TimeUnit timeUnit, + final long expiryDuration) { + this(attributeRepository, createCacheConfiguration(new Duration(timeUnit, expiryDuration))); + } + + /** + * Instantiates a new caching attributes principal factory. + * + * @param attributeRepository the attribute repository + * @param config the config + */ + public CachingPrincipalAttributesRepository(final IPersonAttributeDao attributeRepository, + final MutableConfiguration> config) { + this(attributeRepository, config, Caching.getCachingProvider().getCacheManager()); + } + + /** + * Instantiates a new caching attributes principal factory. + * + * @param attributeRepository the attribute repository + * @param config the config + * @param cacheProviderFullClassName the cache provider full class name + */ + public CachingPrincipalAttributesRepository(final IPersonAttributeDao attributeRepository, + final MutableConfiguration> config, + final String cacheProviderFullClassName) { + this(attributeRepository, config, + Caching.getCachingProvider(cacheProviderFullClassName).getCacheManager()); + } + + /** + * Instantiates a new caching attributes principal factory. + * + * @param attributeRepository the attribute repository + * @param config the config + * @param manager the manager + */ + public CachingPrincipalAttributesRepository(final IPersonAttributeDao attributeRepository, + final MutableConfiguration> config, + final CacheManager manager) { + this.attributeRepository = attributeRepository; + this.cache = manager.createCache(this.cacheName, config); + } + + /** + * Instantiates a new caching attributes principal factory. + * + * @param attributeRepository the attribute repository + * @param cache the cache + */ + public CachingPrincipalAttributesRepository(final IPersonAttributeDao attributeRepository, + final Cache> cache) { + this.attributeRepository = attributeRepository; + this.cache = cache; + } + + /** + * Gets attribute repository. + * + * @return the attribute repository + */ + public IPersonAttributeDao getAttributeRepository() { + return this.attributeRepository; + } + + /** + * Gets cache configuration. + * + * @return the configuration + */ + @JsonIgnore + public MutableConfiguration> getConfiguration() { + return this.cache.getConfiguration(MutableConfiguration.class); + } + + /** + * The merging strategy that deals with existing principal attributes + * and those that are retrieved from the source. By default, existing attributes + * are ignored and the source is always consulted. + * @param mergingStrategy the strategy to use for conflicts + */ + public void setMergingStrategy(final IAttributeMerger mergingStrategy) { + this.mergingStrategy = mergingStrategy; + } + + public IAttributeMerger getMergingStrategy() { + return mergingStrategy; + } + + /** + * Prep cache configuration. + * + * @param expiryDuration the expiry duration + * @return the mutable configuration + */ + protected static MutableConfiguration> createCacheConfiguration(final Duration expiryDuration) { + final MutableConfiguration> config = new MutableConfiguration<>(); + config.setStatisticsEnabled(true); + config.setManagementEnabled(true); + config.setStoreByValue(true); + config.setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(expiryDuration)); + return config; + } + + /** + * Set/add the received attributes into the cache. + * @param id the principal id that controls the grouping of the attributes in the cache. + * @param attributes principal attributes to add to the cache + */ + private void addPrincipalAttributesIntoCache(final String id, final Map attributes) { + synchronized (this.cache) { + if (attributes.isEmpty()) { + + this.cache.remove(id); + LOGGER.debug("No attributes are provided, so removed principal id [{}] from the cache", id); + } else { + this.cache.put(id, attributes); + LOGGER.debug("Cached [{}] attributes for the principal id [{}]", attributes.size(), id); + } + } + } + + @Override + public Map getAttributes(@NotNull final Principal p) { + final Map cachedAttributes = this.cache.get(p.getId()); + if (cachedAttributes != null) { + LOGGER.debug("Found [{}] cached attributes for principal [{}]", cachedAttributes.size(), p.getId()); + return cachedAttributes; + } + + final Map> sourceAttributes = retrievePersonAttributesToPrincipalAttributes(p.getId()); + LOGGER.debug("Found [{}] attributes for principal [{}] from the attribute repository.", + sourceAttributes.size(), p.getId()); + + if (this.mergingStrategy == null) { + LOGGER.debug("No merging strategy found, so attributes retrieved from the repository will be used instead."); + final Map finalAttributes = convertPersonAttributesToPrincipalAttributes(sourceAttributes); + addPrincipalAttributesIntoCache(p.getId(), finalAttributes); + return finalAttributes; + } + + final Map> principalAttributes = convertPrincipalAttributesToPersonAttributes(p); + + LOGGER.debug("Merging current principal attributes with that of the repository via strategy [{}]", + this.mergingStrategy.getClass().getSimpleName()); + final Map> mergedAttributes = + this.mergingStrategy.mergeAttributes(principalAttributes, sourceAttributes); + + final Map finalAttributes = convertPersonAttributesToPrincipalAttributes(mergedAttributes); + addPrincipalAttributesIntoCache(p.getId(), finalAttributes); + return finalAttributes; + + } + + /*** + * Convert principal attributes to person attributes. + * @param p the principal carrying attributes + * @return person attributes + */ + private Map> convertPrincipalAttributesToPersonAttributes(final Principal p) { + final Map> convertedAttributes = new HashMap<>(p.getAttributes().size()); + final Map principalAttributes = p.getAttributes(); + + for (final Map.Entry entry : principalAttributes.entrySet()) { + final Object values = entry.getValue(); + final String key = entry.getKey(); + if (values instanceof List) { + convertedAttributes.put(key, (List) values); + } else { + convertedAttributes.put(key, Collections.singletonList(values)); + } + } + return convertedAttributes; + } + + /** + * Convert person attributes to principal attributes. + * @param attributes person attributes + * @return principal attributes + */ + private Map convertPersonAttributesToPrincipalAttributes(final Map> attributes) { + final Map convertedAttributes = new HashMap<>(); + for (final Map.Entry> entry : attributes.entrySet()) { + final List values = entry.getValue(); + convertedAttributes.put(entry.getKey(), values.size() == 1 ? values.get(0) : values); + } + return convertedAttributes; + } + + /** + * Obtains attributes first from the repository by calling + * {@link org.jasig.services.persondir.IPersonAttributeDao#getPerson(String)}. + * + * @param id the person id to locate in the attribute repository + * @return the map of attributes + */ + private Map> retrievePersonAttributesToPrincipalAttributes(final String id) { + + final IPersonAttributes attrs = this.attributeRepository.getPerson(id); + if (attrs == null) { + LOGGER.debug("Could not find principal [{}] in the repository so no attributes are returned.", id); + return Collections.emptyMap(); + } + + final Map> attributes = attrs.getAttributes(); + if (attributes == null) { + LOGGER.debug("Principal [{}] has no attributes and so none are returned.", id); + return Collections.emptyMap(); + } + return attributes; + } + + @Override + public void close() throws IOException { + this.cache.close(); + this.cache.getCacheManager().close(); + } + + @Override + protected void finalize() throws Throwable { + close(); + super.finalize(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/ChainingPrincipalResolver.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/ChainingPrincipalResolver.java new file mode 100644 index 000000000000..da6850390691 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/ChainingPrincipalResolver.java @@ -0,0 +1,109 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import java.util.List; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import org.jasig.cas.authentication.Credential; + +/** + * Delegates to one or more principal resolves in series to resolve a principal. The input to first configured resolver + * is the authenticated credential; for every subsequent resolver, the input is a {@link Credential} whose ID is the + * resolved princpial ID of the previous resolver. + *

+ * A common use case for this component is resolving a temporary principal ID from an X.509 credential followed by + * a search (e.g. LDAP, database) for the final principal based on the temporary ID. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class ChainingPrincipalResolver implements PrincipalResolver { + + /** The chain of delegate resolvers that are invoked in order. */ + @NotNull + @Size(min = 1) + private List chain; + + /** + * Sets the resolver chain. The resolvers other than the first one MUST be capable of performing resolution + * on the basis of {@link org.jasig.cas.authentication.Credential#getId()} alone; + * {@link PersonDirectoryPrincipalResolver} notably meets that requirement. + * + * @param chain List of delegate resolvers that are invoked in a chain. + */ + public void setChain(final List chain) { + this.chain = chain; + } + + /** + * Resolves a credential by delegating to each of the configured resolvers in sequence. Note that the + * {@link PrincipalResolver#supports(org.jasig.cas.authentication.Credential)} method is called only for the + * first configured resolver. + * + * @param credential Authenticated credential. + * + * @return The principal from the last configured resolver in the chain. + */ + public Principal resolve(final Credential credential) { + Principal result = null; + Credential input = credential; + for (final PrincipalResolver resolver : this.chain) { + if (result != null) { + input = new IdentifiableCredential(result.getId()); + } + result = resolver.resolve(input); + } + return result; + } + + /** + * Determines whether the credential is supported by this component by delegating to the first configured + * resolver in the chain. + * + * @param credential The credential to check for support. + * + * @return True if the first configured resolver in the chain supports the credential, false otherwise. + */ + public boolean supports(final Credential credential) { + return this.chain.get(0).supports(credential); + } + + /** Credential that stores only an ID. */ + static class IdentifiableCredential implements Credential { + /** Credential identifier. */ + private final String id; + + /** + * Creates a new instance with the given ID. + * + * @param id the credential id + */ + public IdentifiableCredential(final String id) { + this.id = id; + } + + @Override + public String getId() { + return this.id; + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/DefaultPrincipalAttributesRepository.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/DefaultPrincipalAttributesRepository.java new file mode 100644 index 000000000000..69e74c2dc395 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/DefaultPrincipalAttributesRepository.java @@ -0,0 +1,36 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import java.util.Map; + +/** + * Default implementation of {@link PrincipalAttributesRepository} + * that just returns the attributes as it receives them. + * @author Misagh Moayyed + * @since 4.1 + */ +public final class DefaultPrincipalAttributesRepository implements PrincipalAttributesRepository { + private static final long serialVersionUID = -4535358847021241725L; + + @Override + public Map getAttributes(final Principal p) { + return p.getAttributes(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/DefaultPrincipalFactory.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/DefaultPrincipalFactory.java new file mode 100644 index 000000000000..f5a232b00eaa --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/DefaultPrincipalFactory.java @@ -0,0 +1,42 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import java.util.Collections; +import java.util.Map; + +/** + * Factory to create {@link org.jasig.cas.authentication.principal.SimplePrincipal} objects. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class DefaultPrincipalFactory implements PrincipalFactory { + private static final long serialVersionUID = -3999695695604948495L; + + @Override + public Principal createPrincipal(final String id) { + return new SimplePrincipal(id, Collections.EMPTY_MAP); + } + + @Override + public Principal createPrincipal(final String id, final Map attributes) { + return new SimplePrincipal(id, attributes); + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/DefaultResponse.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/DefaultResponse.java new file mode 100644 index 000000000000..ffb32ec930fe --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/DefaultResponse.java @@ -0,0 +1,157 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URLEncoder; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Encapsulates a Response to send back for a particular service. + * + * @author Scott Battaglia + * @author Arnaud Lesueur + * @since 3.1 + */ +public final class DefaultResponse implements Response { + /** Log instance. */ + protected static final Logger LOGGER = LoggerFactory.getLogger(DefaultResponse.class); + + /** Pattern to detect unprintable ASCII characters. */ + private static final Pattern NON_PRINTABLE = Pattern.compile("[\\x00-\\x1F\\x7F]+"); + private static final int CONST_REDIRECT_RESPONSE_MULTIPLIER = 40; + private static final int CONST_REDIRECT_RESPONSE_BUFFER = 100; + private static final long serialVersionUID = -8251042088720603062L; + + private final ResponseType responseType; + + private final String url; + + private final Map attributes; + + /** + * Instantiates a new response. + * + * @param responseType the response type + * @param url the url + * @param attributes the attributes + */ + protected DefaultResponse(final ResponseType responseType, final String url, final Map attributes) { + this.responseType = responseType; + this.url = url; + this.attributes = attributes; + } + + /** + * Gets the post response. + * + * @param url the url + * @param attributes the attributes + * @return the post response + */ + public static Response getPostResponse(final String url, final Map attributes) { + return new DefaultResponse(ResponseType.POST, url, attributes); + } + + /** + * Gets the redirect response. + * + * @param url the url + * @param parameters the parameters + * @return the redirect response + */ + public static Response getRedirectResponse(final String url, final Map parameters) { + final StringBuilder builder = new StringBuilder(parameters.size() + * CONST_REDIRECT_RESPONSE_MULTIPLIER + CONST_REDIRECT_RESPONSE_BUFFER); + boolean isFirst = true; + final String[] fragmentSplit = sanitizeUrl(url).split("#"); + + builder.append(fragmentSplit[0]); + + for (final Map.Entry entry : parameters.entrySet()) { + if (entry.getValue() != null) { + if (isFirst) { + builder.append(url.contains("?") ? "&" : "?"); + isFirst = false; + } else { + builder.append('&'); + } + builder.append(entry.getKey()); + builder.append('='); + + try { + builder.append(URLEncoder.encode(entry.getValue(), "UTF-8")); + } catch (final Exception e) { + builder.append(entry.getValue()); + } + } + } + + if (fragmentSplit.length > 1) { + builder.append('#'); + builder.append(fragmentSplit[1]); + } + + return new DefaultResponse(ResponseType.REDIRECT, builder.toString(), parameters); + } + + @Override + public Map getAttributes() { + return this.attributes; + } + + @Override + public Response.ResponseType getResponseType() { + return this.responseType; + } + + @Override + public String getUrl() { + return this.url; + } + + /** + * Sanitize a URL provided by a relying party by normalizing non-printable + * ASCII character sequences into spaces. This functionality protects + * against CRLF attacks and other similar attacks using invisible characters + * that could be abused to trick user agents. + * + * @param url URL to sanitize. + * + * @return Sanitized URL string. + */ + private static String sanitizeUrl(final String url) { + final Matcher m = NON_PRINTABLE.matcher(url); + final StringBuffer sb = new StringBuffer(url.length()); + boolean hasNonPrintable = false; + while (m.find()) { + m.appendReplacement(sb, " "); + hasNonPrintable = true; + } + m.appendTail(sb); + if (hasNonPrintable) { + LOGGER.warn("The following redirect URL has been sanitized and may be sign of attack:\n{}", url); + } + return sb.toString(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/NullPrincipal.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/NullPrincipal.java new file mode 100644 index 000000000000..006e0a9b06e4 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/NullPrincipal.java @@ -0,0 +1,72 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import java.util.Collections; +import java.util.Map; + +/** + * Null principal implementation that allows us to construct {@link org.jasig.cas.authentication.Authentication}s in the event that no + * principal is resolved during the authentication process. + * @author Marvin S. Addison + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class NullPrincipal implements Principal { + + private static final long serialVersionUID = 2309300426720915104L; + + /** The nobody principal. */ + private static final String NOBODY = "nobody"; + + /** The singleton instance. **/ + private static NullPrincipal INSTANCE; + + private final Map attributes; + + /** + * Instantiates a new Null principal. + */ + private NullPrincipal() { + attributes = Collections.emptyMap(); + } + + /** + * Returns the single instance of this class. Will create + * one if none exists. + * + * @return the instance + */ + public static NullPrincipal getInstance() { + if (INSTANCE == null) { + INSTANCE = new NullPrincipal(); + } + return INSTANCE; + } + + @Override + public String getId() { + return NOBODY; + } + + @Override + public Map getAttributes() { + return this.attributes; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/PersonDirectoryPrincipalResolver.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/PersonDirectoryPrincipalResolver.java new file mode 100644 index 000000000000..adae653ee766 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/PersonDirectoryPrincipalResolver.java @@ -0,0 +1,154 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import org.jasig.cas.authentication.Credential; +import org.jasig.services.persondir.IPersonAttributeDao; +import org.jasig.services.persondir.IPersonAttributes; +import org.jasig.services.persondir.support.StubPersonAttributeDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.validation.constraints.NotNull; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Resolves principals by querying a data source using the Jasig + * Person Directory API. + * The {@link org.jasig.cas.authentication.principal.Principal#getAttributes()} are populated by the results of the + * query and the principal ID may optionally be set by proving an attribute whose first non-null value is used; + * otherwise the credential ID is used for the principal ID. + * + * @author Marvin S. Addison + * @since 4.0.0 + * + */ +public class PersonDirectoryPrincipalResolver implements PrincipalResolver { + + /** Log instance. */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private boolean returnNullIfNoAttributes; + + /** Repository of principal attributes to be retrieved. */ + @NotNull + private IPersonAttributeDao attributeRepository = new StubPersonAttributeDao(new HashMap>()); + + /** Factory to create the principal type. **/ + @NotNull + private PrincipalFactory principalFactory = new DefaultPrincipalFactory(); + + /** Optional principal attribute name. */ + private String principalAttributeName; + + @Override + public boolean supports(final Credential credential) { + return true; + } + + @Override + public final Principal resolve(final Credential credential) { + logger.debug("Attempting to resolve a principal..."); + + String principalId = extractPrincipalId(credential); + + if (principalId == null) { + logger.debug("Got null for extracted principal ID; returning null."); + return null; + } + + logger.debug("Creating SimplePrincipal for [{}]", principalId); + + final IPersonAttributes personAttributes = this.attributeRepository.getPerson(principalId); + final Map> attributes; + + if (personAttributes == null) { + attributes = null; + } else { + attributes = personAttributes.getAttributes(); + } + + if (attributes == null & !this.returnNullIfNoAttributes) { + return this.principalFactory.createPrincipal(principalId); + } + + if (attributes == null) { + return null; + } + + final Map convertedAttributes = new HashMap<>(); + for (final Map.Entry> entry : attributes.entrySet()) { + final String key = entry.getKey(); + final List values = entry.getValue(); + if (key.equalsIgnoreCase(this.principalAttributeName)) { + if (values.isEmpty()) { + logger.debug("{} is empty, using {} for principal", this.principalAttributeName, principalId); + } else { + principalId = values.get(0).toString(); + logger.debug( + "Found principal attribute value {}; removing {} from attribute map.", + principalId, + this.principalAttributeName); + } + } else { + convertedAttributes.put(key, values.size() == 1 ? values.get(0) : values); + } + } + return this.principalFactory.createPrincipal(principalId, convertedAttributes); + } + + public final void setAttributeRepository(final IPersonAttributeDao attributeRepository) { + this.attributeRepository = attributeRepository; + } + + public void setReturnNullIfNoAttributes(final boolean returnNullIfNoAttributes) { + this.returnNullIfNoAttributes = returnNullIfNoAttributes; + } + + /** + * Sets the name of the attribute whose first non-null value should be used for the principal ID. + * + * @param attribute Name of attribute containing principal ID. + */ + public void setPrincipalAttributeName(final String attribute) { + this.principalAttributeName = attribute; + } + + /** + * Sets principal factory to create principal objects. + * + * @param principalFactory the principal factory + */ + public void setPrincipalFactory(final PrincipalFactory principalFactory) { + this.principalFactory = principalFactory; + } + + /** + * Extracts the id of the user from the provided credential. This method should be overridded by subclasses to + * achieve more sophisticated strategies for producing a principal ID from a credential. + * + * @param credential the credential provided by the user. + * @return the username, or null if it could not be resolved. + */ + protected String extractPrincipalId(final Credential credential) { + return credential.getId(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/RememberMeAuthenticationMetaDataPopulator.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/RememberMeAuthenticationMetaDataPopulator.java new file mode 100644 index 000000000000..24edbb9bb0cc --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/RememberMeAuthenticationMetaDataPopulator.java @@ -0,0 +1,47 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import org.jasig.cas.authentication.AuthenticationBuilder; +import org.jasig.cas.authentication.AuthenticationMetaDataPopulator; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.RememberMeCredential; + +/** + * Determines if the credential provided are for Remember Me Services and then sets the appropriate + * Authentication attribute if remember me services have been requested. + * + * @author Scott Battaglia + * @since 3.2.1 + */ +public final class RememberMeAuthenticationMetaDataPopulator implements AuthenticationMetaDataPopulator { + + @Override + public void populateAttributes(final AuthenticationBuilder builder, final Credential credential) { + final RememberMeCredential r = (RememberMeCredential) credential; + if (r.isRememberMe()) { + builder.addAttribute(RememberMeCredential.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME, Boolean.TRUE); + } + } + + @Override + public boolean supports(final Credential credential) { + return credential instanceof RememberMeCredential; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/ShibbolethCompatiblePersistentIdGenerator.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/ShibbolethCompatiblePersistentIdGenerator.java new file mode 100644 index 000000000000..3e5c9ba26d6e --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/ShibbolethCompatiblePersistentIdGenerator.java @@ -0,0 +1,141 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import com.google.common.io.ByteSource; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.jasig.cas.util.CompressionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.validation.constraints.NotNull; +import java.io.IOException; +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Generates PersistentIds based on the Shibboleth algorithm. + * + * @author Scott Battaglia + * @since 3.1 + */ +public final class ShibbolethCompatiblePersistentIdGenerator implements PersistentIdGenerator { + + private static final long serialVersionUID = 6182838799563190289L; + + /** Log instance. */ + private static final Logger LOGGER = LoggerFactory.getLogger(ShibbolethCompatiblePersistentIdGenerator.class); + + private static final byte CONST_SEPARATOR = (byte) '!'; + + private static final int CONST_DEFAULT_SALT_COUNT = 16; + + private byte[] salt; + + /** + * Instantiates a new shibboleth compatible persistent id generator. + * The salt is initialized to a random 16-digit alphanumeric string. + * The generated id is pseudo-anonymous which allows it to be continually uniquely + * identified by for a particular service. + */ + public ShibbolethCompatiblePersistentIdGenerator() { + this.salt = RandomStringUtils.randomAlphanumeric(CONST_DEFAULT_SALT_COUNT).getBytes(Charset.defaultCharset()); + } + + /** + * Instantiates a new shibboleth compatible persistent id generator. + * + * @param salt the the salt + */ + public ShibbolethCompatiblePersistentIdGenerator(@NotNull final String salt) { + this.salt = salt.getBytes(Charset.defaultCharset()); + } + + /** + * @deprecated As of 4.1. + * Sets salt. + * + * @param salt the salt + */ + @Deprecated + public void setSalt(final String salt) { + this.salt = salt.getBytes(Charset.defaultCharset()); + LOGGER.warn("setSalt() is deprecated and will be removed. Use the constructor instead."); + } + + + /** + * Get salt. + * + * @return the byte[] for the salt or null + */ + public byte[] getSalt() { + try { + return ByteSource.wrap(this.salt).read(); + } catch (final IOException e) { + LOGGER.warn("Salt cannot be read because the byte array from source could not be consumed"); + } + return null; + } + + @Override + public String generate(final Principal principal, final Service service) { + try { + final MessageDigest md = MessageDigest.getInstance("SHA"); + final Charset charset = Charset.defaultCharset(); + md.update(service.getId().getBytes(charset)); + md.update(CONST_SEPARATOR); + md.update(principal.getId().getBytes(charset)); + md.update(CONST_SEPARATOR); + + final String result = CompressionUtils.encodeBase64(md.digest(this.salt)); + return result.replaceAll(System.getProperty("line.separator"), ""); + } catch (final NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj.getClass() != getClass()) { + return false; + } + final ShibbolethCompatiblePersistentIdGenerator rhs = (ShibbolethCompatiblePersistentIdGenerator) obj; + return new EqualsBuilder() + .append(this.salt, rhs.salt) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder() + .append(this.salt) + .toHashCode(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/SimplePrincipal.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/SimplePrincipal.java new file mode 100644 index 000000000000..465e8fc49b42 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/SimplePrincipal.java @@ -0,0 +1,113 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Simple implementation of a {@link Principal} that exposes an unmodifiable + * map of attributes. The attributes are cached upon construction and + * will not be updated unless the principal is entirely and newly + * resolved again. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.1 + */ +public final class SimplePrincipal implements Principal { + /** Serialization support. */ + private static final long serialVersionUID = -1255260750151385796L; + + /** The unique identifier for the principal. */ + private final String id; + + /** Principal attributes. **/ + private final Map attributes; + + /** No-arg constructor for serialization support. */ + private SimplePrincipal() { + this.id = null; + this.attributes = new HashMap<>(); + } + + /** + * Instantiates a new simple principal. + * + * @param id the id + */ + private SimplePrincipal(final String id) { + this(id, Collections.EMPTY_MAP); + } + + /** + * Instantiates a new simple principal. + * + * @param id the id + * @param attributes the attributes + */ + protected SimplePrincipal(final String id, final Map attributes) { + this.id = id; + this.attributes = attributes; + } + + /** + * @return An immutable map of principal attributes + */ + public Map getAttributes() { + return Collections.unmodifiableMap(this.attributes); + } + + @Override + public String toString() { + return this.id; + } + + @Override + public int hashCode() { + final HashCodeBuilder builder = new HashCodeBuilder(83, 31); + builder.append(this.id); + return builder.toHashCode(); + } + + public String getId() { + return this.id; + } + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj.getClass() != getClass()) { + return false; + } + final SimplePrincipal rhs = (SimplePrincipal) obj; + return new EqualsBuilder() + .append(this.id, rhs.id) + .isEquals(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/SimpleWebApplicationServiceImpl.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/SimpleWebApplicationServiceImpl.java new file mode 100644 index 000000000000..16bde377bb0b --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/SimpleWebApplicationServiceImpl.java @@ -0,0 +1,117 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; + +/** + * Represents a service which wishes to use the CAS protocol. + * + * @author Scott Battaglia + * @since 3.1 + */ +public final class SimpleWebApplicationServiceImpl extends AbstractWebApplicationService { + + private static final long serialVersionUID = 8334068957483758042L; + + private static final String CONST_PARAM_SERVICE = "service"; + + private static final String CONST_PARAM_TARGET_SERVICE = "targetService"; + + private static final String CONST_PARAM_TICKET = "ticket"; + + private static final String CONST_PARAM_METHOD = "method"; + + private final Response.ResponseType responseType; + + /** + * Instantiates a new simple web application service impl. + * + * @param id the id + */ + public SimpleWebApplicationServiceImpl(final String id) { + this(id, id, null, null); + } + + /** + * Instantiates a new simple web application service impl. + * + * @param id the id + * @param originalUrl the original url + * @param artifactId the artifact id + * @param responseType the response type + */ + private SimpleWebApplicationServiceImpl(final String id, + final String originalUrl, final String artifactId, + final Response.ResponseType responseType) { + super(id, originalUrl, artifactId); + this.responseType = responseType; + } + + /** + * Creates the service from the request. + * + * @param request the request + * @return the simple web application service impl + */ + public static SimpleWebApplicationServiceImpl createServiceFrom( + final HttpServletRequest request) { + final String targetService = request.getParameter(CONST_PARAM_TARGET_SERVICE); + final String service = request.getParameter(CONST_PARAM_SERVICE); + final String serviceAttribute = (String) request.getAttribute(CONST_PARAM_SERVICE); + final String method = request.getParameter(CONST_PARAM_METHOD); + final String serviceToUse; + if (StringUtils.hasText(targetService)) { + serviceToUse = targetService; + } else if (StringUtils.hasText(service)) { + serviceToUse = service; + } else { + serviceToUse = serviceAttribute; + } + + if (!StringUtils.hasText(serviceToUse)) { + return null; + } + + final String id = cleanupUrl(serviceToUse); + final String artifactId = request.getParameter(CONST_PARAM_TICKET); + + return new SimpleWebApplicationServiceImpl(id, serviceToUse, + artifactId, "POST".equals(method) ? Response.ResponseType.POST + : Response.ResponseType.REDIRECT); + } + + @Override + public Response getResponse(final String ticketId) { + final Map parameters = new HashMap<>(); + + if (StringUtils.hasText(ticketId)) { + parameters.put(CONST_PARAM_TICKET, ticketId); + } + + if (Response.ResponseType.POST == this.responseType) { + return DefaultResponse.getPostResponse(getOriginalUrl(), parameters); + } + return DefaultResponse.getRedirectResponse(getOriginalUrl(), parameters); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/package.html b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/package.html new file mode 100644 index 000000000000..328792c43861 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/package.html @@ -0,0 +1,42 @@ + + + +

Credentials is a marker interface for an opaque object that may be recognized by +Handlers and Resolvers. Credentials may be a Userid/Password, Certificate, +RemoteUser, IP address, etc.

+

When the simple AuthenticationManagerImpl is +used, that bean is configured with a list of AuthenticationHandlers that +validate Credentials and CredentialsToPrincipalResolvers that turn Credentials +into Principal objects.

+

The Authentication Handler validates Credentials but does not extract +information. This seems curious in the simple case when the credential are a +Userid/Password. It becomes clearer for a Certificate. A Certificate is valid if +you trust the CA, if it hasn't expired, and if it isn't revoked. You can decide +all this, and still not have the foggiest idea what ID to give to the person (if +it is a person) reprepsented by the Certificate.

+

The CredentialsToPrincipalResolver looks into previously validated +Credentials to construct a Principal object containing an ID (and in more +complex cases some attributes). The DefaultCredentialsToPrincipalResolver takes +UsernamePasswordCredentials and creates a SimplePrincipal containing the Userid.

+ + + diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/support/AbstractCasAttributeEncoder.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/support/AbstractCasAttributeEncoder.java new file mode 100644 index 000000000000..e8f5d5bed5f8 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/support/AbstractCasAttributeEncoder.java @@ -0,0 +1,140 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.authentication.support; + +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.util.services.RegisteredServiceCipherExecutor; +import org.jasig.cas.util.services.DefaultRegisteredServiceCipherExecutor; +import org.jasig.cas.web.view.CasViewConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.validation.constraints.NotNull; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * Abstract class to define common attribute encoding operations. + * + * @author Misagh Moayyed + * @since 4.1 + */ +public abstract class AbstractCasAttributeEncoder implements CasAttributeEncoder { + + /** The Services manager. */ + @NotNull + protected final ServicesManager servicesManager; + + /** The Logger. */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private RegisteredServiceCipherExecutor cipherExecutor; + + /** + * Instantiates a new attribute encoder with the default + * cipher as {@link DefaultRegisteredServiceCipherExecutor}. + * @param servicesManager the services manager + */ + public AbstractCasAttributeEncoder(final ServicesManager servicesManager) { + this(servicesManager, new DefaultRegisteredServiceCipherExecutor()); + } + + /** + * Instantiates a new Abstract cas attribute encoder. + * + * @param servicesManager the services manager + * @param cipherExecutor the cipher executor + */ + public AbstractCasAttributeEncoder(final ServicesManager servicesManager, + final RegisteredServiceCipherExecutor cipherExecutor) { + this.servicesManager = servicesManager; + this.cipherExecutor = cipherExecutor; + } + + @Override + public final Map encodeAttributes(final Map attributes, + final Service service) { + logger.debug("Starting to encode attributes for release to service [{}]", service); + final Map newEncodedAttributes = new HashMap<>(attributes); + final Map cachedAttributesToEncode = initialize(newEncodedAttributes); + + final RegisteredService registeredService = this.servicesManager.findServiceBy(service); + if (registeredService != null && registeredService.getAccessStrategy().isServiceAccessAllowed()) { + encodeAttributesInternal(newEncodedAttributes, cachedAttributesToEncode, + this.cipherExecutor, registeredService); + logger.debug("[{}] Encoded attributes are available for release to [{}]", + newEncodedAttributes.size(), service); + } else { + logger.debug("Service [{}] is not found and/or enabled in the service registry. " + + "No encoding has taken place.", service); + } + + return newEncodedAttributes; + } + + /** + * Initialize the cipher with the public key + * and then start to encrypt select attributes. + * + * @param attributes the attributes + * @param cachedAttributesToEncode the cached attributes to encode + * @param cipher the cipher object initialized per service public key + * @param registeredService the registered service + */ + protected abstract void encodeAttributesInternal(final Map attributes, + final Map cachedAttributesToEncode, + final RegisteredServiceCipherExecutor cipher, + final RegisteredService registeredService); + + + /** + * Initialize the encoding process. Removes the + * {@link org.jasig.cas.web.view.CasViewConstants#MODEL_ATTRIBUTE_NAME_PRINCIPAL_CREDENTIAL} + * and + * {@link org.jasig.cas.web.view.CasViewConstants#MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET} + * from the authentication attributes originally and into a cache object, so it + * can later on be encrypted if needed. + * @param attributes the new encoded attributes + * @return a map of attributes that are to be encoded and encrypted + */ + protected final Map initialize(final Map attributes) { + final Map cachedAttributesToEncode = new HashMap<>(attributes.size()); + + Collection collection = (Collection) attributes.remove(CasViewConstants.MODEL_ATTRIBUTE_NAME_PRINCIPAL_CREDENTIAL); + if (collection != null && collection.size() == 1) { + cachedAttributesToEncode.put(CasViewConstants.MODEL_ATTRIBUTE_NAME_PRINCIPAL_CREDENTIAL, + collection.iterator().next().toString()); + logger.debug("Removed [{}] as an authentication attribute and cached it locally.", + CasViewConstants.MODEL_ATTRIBUTE_NAME_PRINCIPAL_CREDENTIAL); + } + + collection = (Collection) attributes.remove(CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET); + if (collection != null && collection.size() == 1) { + cachedAttributesToEncode.put(CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET, + collection.iterator().next().toString()); + logger.debug("Removed [{}] as an authentication attribute and cached it locally.", + CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET); + } + return cachedAttributesToEncode; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/support/DefaultCasAttributeEncoder.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/support/DefaultCasAttributeEncoder.java new file mode 100644 index 000000000000..003f772fc58f --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/support/DefaultCasAttributeEncoder.java @@ -0,0 +1,133 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.authentication.support; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.util.services.RegisteredServiceCipherExecutor; +import org.jasig.cas.web.view.CasViewConstants; +import java.util.Map; + +/** + * The default implementation of the attribute + * encoder that will use a per-service key-pair + * to encrypt the credential password and PGT + * when available. All other attributes remain in + * place. + * + * @author Misagh Moayyed + * @since 4.1 + */ +public class DefaultCasAttributeEncoder extends AbstractCasAttributeEncoder { + + /** + * Instantiates a new Default cas attribute encoder. + * + * @param servicesManager the services manager + */ + public DefaultCasAttributeEncoder(final ServicesManager servicesManager) { + super(servicesManager); + } + + /** + * Instantiates a new Default cas attribute encoder. + * + * @param servicesManager the services manager + * @param cipherExecutor the cipher executor + */ + public DefaultCasAttributeEncoder(final ServicesManager servicesManager, + final RegisteredServiceCipherExecutor cipherExecutor) { + super(servicesManager, cipherExecutor); + } + + /** + * Encode and encrypt credential password using the public key + * supplied by the service. The result is base64 encoded + * and put into the attributes collection again, overwriting + * the previous value. + * + * @param attributes the attributes + * @param cachedAttributesToEncode the cached attributes to encode + * @param cipher the cipher + * @param registeredService the registered service + */ + protected final void encodeAndEncryptCredentialPassword(final Map attributes, + final Map cachedAttributesToEncode, + final RegisteredServiceCipherExecutor cipher, + final RegisteredService registeredService) { + encryptAndEncodeAndPutIntoAttributesMap(attributes, cachedAttributesToEncode, + CasViewConstants.MODEL_ATTRIBUTE_NAME_PRINCIPAL_CREDENTIAL, + cipher, registeredService); + } + + /** + * Encode and encrypt pgt. + * + * @param attributes the attributes + * @param cachedAttributesToEncode the cached attributes to encode + * @param cipher the cipher + * @param registeredService the registered service + */ + protected final void encodeAndEncryptProxyGrantingTicket(final Map attributes, + final Map cachedAttributesToEncode, + final RegisteredServiceCipherExecutor cipher, + final RegisteredService registeredService) { + encryptAndEncodeAndPutIntoAttributesMap(attributes, cachedAttributesToEncode, + CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET, cipher, registeredService); + } + + /** + * Encrypt, encode and put the attribute into attributes map. + * + * @param attributes the attributes + * @param cachedAttributesToEncode the cached attributes to encode + * @param cachedAttributeName the cached attribute name + * @param cipher the cipher + * @param registeredService the registered service + */ + protected final void encryptAndEncodeAndPutIntoAttributesMap(final Map attributes, + final Map cachedAttributesToEncode, + final String cachedAttributeName, + final RegisteredServiceCipherExecutor cipher, + final RegisteredService registeredService) { + final String cachedAttribute = cachedAttributesToEncode.remove(cachedAttributeName); + if (StringUtils.isNotBlank(cachedAttribute)) { + logger.debug("Retrieved [{}] as a cached model attribute...", cachedAttributeName); + final String encodedValue = cipher.encode(cachedAttribute, registeredService); + if (StringUtils.isNotBlank(encodedValue)) { + attributes.put(cachedAttributeName, encodedValue); + logger.debug("Encrypted and encoded [{}] as an attribute to [{}].", + cachedAttributeName, encodedValue); + } + } else { + logger.debug("[{}] is not available as a cached model attribute to encrypt...", cachedAttributeName); + } + } + + @Override + protected void encodeAttributesInternal(final Map attributes, + final Map cachedAttributesToEncode, + final RegisteredServiceCipherExecutor cipher, + final RegisteredService registeredService) { + encodeAndEncryptCredentialPassword(attributes, cachedAttributesToEncode, cipher, registeredService); + encodeAndEncryptProxyGrantingTicket(attributes, cachedAttributesToEncode, cipher, registeredService); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/support/PasswordExpiringWarningMessageDescriptor.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/support/PasswordExpiringWarningMessageDescriptor.java new file mode 100644 index 000000000000..ee14c2ab0aca --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/support/PasswordExpiringWarningMessageDescriptor.java @@ -0,0 +1,55 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.support; + +import org.jasig.cas.DefaultMessageDescriptor; + +/** + * Message conveying account password expiration warning details. + * + * @author Misagh Moayyed + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class PasswordExpiringWarningMessageDescriptor extends DefaultMessageDescriptor { + /** Serialization version marker. */ + private static final long serialVersionUID = -5892600936676838470L; + + /** Message bundle code. */ + private static final String CODE = "password.expiration.warning"; + + /** + * Creates a new instance. + * + * @param defaultMsg Default warning message. + * @param days Days to password expiration. + * @param passwordChangeUrl Password change URL. + */ + public PasswordExpiringWarningMessageDescriptor(final String defaultMsg, final long days, final String passwordChangeUrl) { + super(CODE, defaultMsg, days, passwordChangeUrl); + } + + public long getDaysToExpiration() { + return (Long) getParams()[0]; + } + + public String getPasswordChangeUrl() { + return (String) getParams()[1]; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/support/PasswordPolicyConfiguration.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/support/PasswordPolicyConfiguration.java new file mode 100644 index 000000000000..ba4903e2d746 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/support/PasswordPolicyConfiguration.java @@ -0,0 +1,71 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.support; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Container for password policy configuration. + * + * @author Misagh Moayyed + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class PasswordPolicyConfiguration { + + private static final int DEFAULT_PASSWORD_WARNING_NUMBER_OF_DAYS = 30; + + /** Logger instance. */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** Disregard the warning period and warn all users of password expiration. */ + private boolean alwaysDisplayPasswordExpirationWarning; + + /** Threshold number of days till password expiration below which a warning is displayed. **/ + private int passwordWarningNumberOfDays = DEFAULT_PASSWORD_WARNING_NUMBER_OF_DAYS; + + /** Url to the password policy application. **/ + private String passwordPolicyUrl; + + + public boolean isAlwaysDisplayPasswordExpirationWarning() { + return this.alwaysDisplayPasswordExpirationWarning; + } + + public void setAlwaysDisplayPasswordExpirationWarning(final boolean alwaysDisplayPasswordExpirationWarning) { + this.alwaysDisplayPasswordExpirationWarning = alwaysDisplayPasswordExpirationWarning; + } + + public String getPasswordPolicyUrl() { + return this.passwordPolicyUrl; + } + + public void setPasswordPolicyUrl(final String passwordPolicyUrl) { + this.passwordPolicyUrl = passwordPolicyUrl; + } + + public int getPasswordWarningNumberOfDays() { + return passwordWarningNumberOfDays; + } + + public void setPasswordWarningNumberOfDays(final int days) { + this.passwordWarningNumberOfDays = days; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/logout/DefaultLogoutRequest.java b/cas-server-core/src/main/java/org/jasig/cas/logout/DefaultLogoutRequest.java new file mode 100644 index 000000000000..410b1ce50304 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/logout/DefaultLogoutRequest.java @@ -0,0 +1,95 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.logout; + +import java.net.URL; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.jasig.cas.authentication.principal.SingleLogoutService; + +/** + * Define a logout request for a service accessed by a user. + * + * @author Jerome Leleu + * @since 4.0.0 + */ +public final class DefaultLogoutRequest implements LogoutRequest { + + /** Generated serialVersionUID. */ + private static final long serialVersionUID = -6411421298859045022L; + + /** The service ticket id. */ + private final String ticketId; + + /** The service. */ + private final SingleLogoutService service; + + /** The status of the logout request. */ + private LogoutRequestStatus status = LogoutRequestStatus.NOT_ATTEMPTED; + + private final URL logoutUrl; + + /** + * Build a logout request from ticket identifier and service. + * Default status is {@link LogoutRequestStatus#NOT_ATTEMPTED}. + * + * @param ticketId the service ticket id. + * @param service the service. + * @param logoutUrl the logout url + */ + public DefaultLogoutRequest(final String ticketId, final SingleLogoutService service, final URL logoutUrl) { + this.ticketId = ticketId; + this.service = service; + this.logoutUrl = logoutUrl; + } + + @Override + public LogoutRequestStatus getStatus() { + return status; + } + + @Override + public void setStatus(final LogoutRequestStatus status) { + this.status = status; + } + + @Override + public String getTicketId() { + return ticketId; + } + + @Override + public SingleLogoutService getService() { + return service; + } + + @Override + public URL getLogoutUrl() { + return logoutUrl; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("ticketId", ticketId) + .append("service", service) + .append("status", status) + .toString(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/logout/LogoutManagerImpl.java b/cas-server-core/src/main/java/org/jasig/cas/logout/LogoutManagerImpl.java new file mode 100644 index 000000000000..6d885da79d72 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/logout/LogoutManagerImpl.java @@ -0,0 +1,294 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.logout; + +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.authentication.principal.SingleLogoutService; +import org.jasig.cas.services.LogoutType; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.util.CompressionUtils; +import org.jasig.cas.util.http.HttpClient; +import org.jasig.cas.util.http.HttpMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; + +import javax.validation.constraints.NotNull; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * This logout manager handles the Single Log Out process. + * + * @author Jerome Leleu + * @since 4.0.0 + */ +public final class LogoutManagerImpl implements LogoutManager { + + /** The logger. */ + private static final Logger LOGGER = LoggerFactory.getLogger(LogoutManagerImpl.class); + + /** The parameter name that contains the logout request. */ + private static final String LOGOUT_PARAMETER_NAME = "logoutRequest"; + + /** ASCII character set. */ + private static final Charset ASCII = Charset.forName("ASCII"); + + /** The services manager. */ + @NotNull + private final ServicesManager servicesManager; + + /** An HTTP client. */ + @NotNull + private final HttpClient httpClient; + + @NotNull + private final LogoutMessageCreator logoutMessageBuilder; + + /** Whether single sign out is disabled or not. */ + private boolean singleLogoutCallbacksDisabled; + + /** + * Whether messages to endpoints would be sent in an asynchronous fashion. + * True by default. + **/ + private boolean asynchronous = true; + + /** + * Build the logout manager. + * @param servicesManager the services manager. + * @param httpClient an HTTP client. + * @param logoutMessageBuilder the builder to construct logout messages. + */ + public LogoutManagerImpl(final ServicesManager servicesManager, final HttpClient httpClient, + final LogoutMessageCreator logoutMessageBuilder) { + this.servicesManager = servicesManager; + this.httpClient = httpClient; + this.logoutMessageBuilder = logoutMessageBuilder; + } + + /** + * Set if messages are sent in an asynchronous fashion. + * + * @param asyncCallbacks if message is synchronously sent + * @since 4.1.0 + */ + public void setAsynchronous(final boolean asyncCallbacks) { + this.asynchronous = asyncCallbacks; + } + + /** + * Set if messages are sent in an asynchronous fashion. + * + * @param asyncCallbacks if message is synchronously sent + * @deprecated As of 4.1. Use {@link #setAsynchronous(boolean)} instead + */ + @Deprecated + public void setIssueAsynchronousCallbacks(final boolean asyncCallbacks) { + this.asynchronous = asyncCallbacks; + LOGGER.warn("setIssueAsynchronousCallbacks() is deprecated. Use setAsynchronous() instead."); + } + + /** + * Perform a back channel logout for a given ticket granting ticket and returns all the logout requests. + * + * @param ticket a given ticket granting ticket. + * @return all logout requests. + */ + @Override + public List performLogout(final TicketGrantingTicket ticket) { + final Map services; + // synchronize the retrieval of the services and their cleaning for the TGT + // to avoid concurrent logout mess ups + synchronized (ticket) { + services = ticket.getServices(); + ticket.removeAllServices(); + } + ticket.markTicketExpired(); + + final List logoutRequests = new ArrayList<>(); + // if SLO is not disabled + if (!this.singleLogoutCallbacksDisabled) { + // through all services + for (final Map.Entry entry : services.entrySet()) { + // it's a SingleLogoutService, else ignore + final Service service = entry.getValue(); + if (service instanceof SingleLogoutService) { + final LogoutRequest logoutRequest = handleLogoutForSloService((SingleLogoutService) service, entry.getKey()); + if (logoutRequest != null) { + LOGGER.debug("Captured logout request [{}]", logoutRequest); + logoutRequests.add(logoutRequest); + } + } + } + } + + return logoutRequests; + } + + /** + * Service supports back channel single logout? + * Service must be found in the registry. enabled and logout type must not be {@link LogoutType#NONE}. + * @param registeredService the registered service + * @return true, if support is available. + */ + private boolean serviceSupportsSingleLogout(final RegisteredService registeredService) { + return registeredService != null + && registeredService.getAccessStrategy().isServiceAccessAllowed() + && registeredService.getLogoutType() != LogoutType.NONE; + } + + /** + * Handle logout for slo service. + * + * @param singleLogoutService the service + * @param ticketId the ticket id + * @return the logout request + */ + private LogoutRequest handleLogoutForSloService(final SingleLogoutService singleLogoutService, final String ticketId) { + if (!singleLogoutService.isLoggedOutAlready()) { + + final RegisteredService registeredService = servicesManager.findServiceBy(singleLogoutService); + if (serviceSupportsSingleLogout(registeredService)) { + + final URL logoutUrl = determineLogoutUrl(registeredService, singleLogoutService); + final DefaultLogoutRequest logoutRequest = new DefaultLogoutRequest(ticketId, singleLogoutService, logoutUrl); + final LogoutType type = registeredService.getLogoutType() == null + ? LogoutType.BACK_CHANNEL : registeredService.getLogoutType(); + + switch (type) { + case BACK_CHANNEL: + if (performBackChannelLogout(logoutRequest)) { + logoutRequest.setStatus(LogoutRequestStatus.SUCCESS); + } else { + logoutRequest.setStatus(LogoutRequestStatus.FAILURE); + LOGGER.warn("Logout message not sent to [{}]; Continuing processing...", singleLogoutService.getId()); + } + break; + default: + logoutRequest.setStatus(LogoutRequestStatus.NOT_ATTEMPTED); + break; + } + return logoutRequest; + } + + } + return null; + } + + /** + * Determine logout url. + * + * @param registeredService the registered service + * @param singleLogoutService the single logout service + * @return the uRL + */ + private URL determineLogoutUrl(final RegisteredService registeredService, final SingleLogoutService singleLogoutService) { + try { + URL logoutUrl = new URL(singleLogoutService.getOriginalUrl()); + final URL serviceLogoutUrl = registeredService.getLogoutUrl(); + + if (serviceLogoutUrl != null) { + LOGGER.debug("Logout request will be sent to [{}] for service [{}]", + serviceLogoutUrl, singleLogoutService); + logoutUrl = serviceLogoutUrl; + } + return logoutUrl; + } catch (final Exception e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Log out of a service through back channel. + * + * @param request the logout request. + * @return if the logout has been performed. + */ + private boolean performBackChannelLogout(final LogoutRequest request) { + try { + final String logoutRequest = this.logoutMessageBuilder.create(request); + final SingleLogoutService logoutService = request.getService(); + logoutService.setLoggedOutAlready(true); + + LOGGER.debug("Sending logout request for: [{}]", logoutService.getId()); + final LogoutHttpMessage msg = new LogoutHttpMessage(request.getLogoutUrl(), logoutRequest); + LOGGER.debug("Prepared logout message to send is [{}]", msg); + return this.httpClient.sendMessageToEndPoint(msg); + } catch (final Exception e) { + LOGGER.error(e.getMessage(), e); + } + return false; + } + + /** + * Create a logout message for front channel logout. + * + * @param logoutRequest the logout request. + * @return a front SAML logout message. + */ + public String createFrontChannelLogoutMessage(final LogoutRequest logoutRequest) { + final String logoutMessage = this.logoutMessageBuilder.create(logoutRequest); + LOGGER.trace("Attempting to deflate the logout message [{}]", logoutMessage); + return CompressionUtils.deflate(logoutMessage); + } + + /** + * Set if the logout is disabled. + * + * @param singleLogoutCallbacksDisabled if the logout is disabled. + */ + public void setSingleLogoutCallbacksDisabled(final boolean singleLogoutCallbacksDisabled) { + this.singleLogoutCallbacksDisabled = singleLogoutCallbacksDisabled; + } + + /** + * A logout http message that is accompanied by a special content type + * and formatting. + * @since 4.1.0 + */ + private final class LogoutHttpMessage extends HttpMessage { + + /** + * Constructs a logout message, whose method of submission + * is controlled by the {@link LogoutManagerImpl#asynchronous}. + * + * @param url The url to send the message to + * @param message Message to send to the url + */ + public LogoutHttpMessage(final URL url, final String message) { + super(url, message, LogoutManagerImpl.this.asynchronous); + setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE); + } + + /** + * {@inheritDoc}. + * Prepends the string "logoutRequest=" to the message body. + */ + @Override + protected String formatOutputMessageInternal(final String message) { + return LOGOUT_PARAMETER_NAME + "=" + super.formatOutputMessageInternal(message); + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/logout/SamlCompliantLogoutMessageCreator.java b/cas-server-core/src/main/java/org/jasig/cas/logout/SamlCompliantLogoutMessageCreator.java new file mode 100644 index 000000000000..a3131a301a5b --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/logout/SamlCompliantLogoutMessageCreator.java @@ -0,0 +1,57 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.logout; + +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.ISOStandardDateFormat; +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A builder that uses the saml standard's LogoutRequest template in order + * to build the logout request. + * @author Misagh Moayyed + * @see DefaultLogoutRequest + * @since 4.0.0 + */ +public final class SamlCompliantLogoutMessageCreator implements LogoutMessageCreator { + + /** The logger. */ + private static final Logger LOGGER = LoggerFactory.getLogger(SamlCompliantLogoutMessageCreator.class); + + /** A ticket Id generator. */ + private static final UniqueTicketIdGenerator GENERATOR = new DefaultUniqueTicketIdGenerator(); + + /** The logout request template. */ + private static final String LOGOUT_REQUEST_TEMPLATE = + "@NOT_USED@" + + "%s"; + + @Override + public String create(final LogoutRequest request) { + final String logoutRequest = String.format(LOGOUT_REQUEST_TEMPLATE, GENERATOR.getNewTicketId("LR"), + new ISOStandardDateFormat().getCurrentDateAndTime(), request.getTicketId()); + + LOGGER.debug("Generated logout message: [{}]", logoutRequest); + return logoutRequest; + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/monitor/AbstractCacheMonitor.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/AbstractCacheMonitor.java new file mode 100644 index 000000000000..27995b1e58ad --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/AbstractCacheMonitor.java @@ -0,0 +1,113 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +/** + * Abstract base class for monitors that observe cache storage systems. + * + * @author Marvin S. Addison + * @since 3.5.1 + */ +public abstract class AbstractCacheMonitor extends AbstractNamedMonitor { + + /** Default free capacity threshold is 10%. */ + public static final int DEFAULT_WARN_FREE_THRESHOLD = 10; + + /** Default eviction threshold is 0. */ + public static final long DEFAULT_EVICTION_THRESHOLD = 0; + + /** Percent free capacity threshold below which a warning is issued.*/ + private int warnFreeThreshold = DEFAULT_WARN_FREE_THRESHOLD; + + /** Threshold for number of acceptable evictions above which an error is issued. */ + private long evictionThreshold = DEFAULT_EVICTION_THRESHOLD; + + + /** + * Sets the percent free capacity threshold below which a warning is issued. + * + * @param percent Warning threshold percent. + */ + public void setWarnFreeThreshold(final int percent) { + this.warnFreeThreshold = percent; + } + + + /** + * Sets the eviction threshold count above which an error is issued. + * + * @param count Threshold for number of cache evictions. + */ + public void setEvictionThreshold(final long count) { + this.evictionThreshold = count; + } + + @Override + public CacheStatus observe() { + CacheStatus status; + try { + final CacheStatistics[] statistics = getStatistics(); + if (statistics == null || statistics.length == 0) { + return new CacheStatus(StatusCode.ERROR, "Cache statistics not available."); + } + StatusCode overall = StatusCode.OK; + StatusCode code; + for (final CacheStatistics stats : statistics) { + code = status(stats); + // Record highest status which is equivalent to worst case + if (code.value() > overall.value()) { + overall = code; + } + } + status = new CacheStatus(overall, null, statistics); + } catch (final Exception e) { + status = new CacheStatus(e); + } + return status; + } + + + /** + * Gets the statistics from this monitor. + * + * @return the statistics + */ + protected abstract CacheStatistics[] getStatistics(); + + + /** + * Computes the status code for a given set of cache statistics. + * + * @param statistics Cache statistics. + * + * @return {@link StatusCode#WARN} if eviction count is above threshold or if + * percent free space is below threshold, otherwise {@link StatusCode#OK}. + */ + protected StatusCode status(final CacheStatistics statistics) { + final StatusCode code; + if (statistics.getEvictions() > this.evictionThreshold) { + code = StatusCode.WARN; + } else if (statistics.getPercentFree() < this.warnFreeThreshold) { + code = StatusCode.WARN; + } else { + code = StatusCode.OK; + } + return code; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/monitor/AbstractNamedMonitor.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/AbstractNamedMonitor.java new file mode 100644 index 000000000000..f3268c265762 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/AbstractNamedMonitor.java @@ -0,0 +1,54 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.Assert; + +/** + * Base class for all monitors that support configurable naming. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +public abstract class AbstractNamedMonitor implements Monitor { + + /** Logger instance. */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** Monitor name. */ + protected String name; + + /** + * @return Monitor name. + */ + public String getName() { + return StringUtils.defaultIfEmpty(this.name, getClass().getSimpleName()); + } + + /** + * @param n Monitor name. + */ + public void setName(final String n) { + Assert.hasText(n, "Monitor name cannot be null or empty."); + this.name = n; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/monitor/AbstractPoolMonitor.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/AbstractPoolMonitor.java new file mode 100644 index 000000000000..f793e6b12cd3 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/AbstractPoolMonitor.java @@ -0,0 +1,135 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import javax.validation.constraints.NotNull; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Describes a monitor that observes a pool of resources. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +public abstract class AbstractPoolMonitor extends AbstractNamedMonitor { + + /** Default maximum wait time for asynchronous pool validation. */ + public static final int DEFAULT_MAX_WAIT = 3000; + + /** Maximum amount of time in ms to wait while validating pool resources. */ + private int maxWait = DEFAULT_MAX_WAIT; + + /** Executor that performs pool resource validation. */ + @NotNull + private ExecutorService executor; + + + /** + * Sets the executor service responsible for pool resource validation. + * + * @param executorService Executor of pool validation operations. + */ + public void setExecutor(final ExecutorService executorService) { + this.executor = executorService; + } + + + /** + * Set the maximum amount of time wait while validating pool resources. + * If the pool defines a minumum time to wait for a resource, this property + * should be set less than that value. + * + * @param time Wait time in milliseconds. + */ + public void setMaxWait(final int time) { + this.maxWait = time; + } + + /** + * {@inheritDoc} + **/ + @Override + public PoolStatus observe() { + final Future result = this.executor.submit(new Validator(this)); + StatusCode code; + String description = null; + try { + code = result.get(this.maxWait, TimeUnit.MILLISECONDS); + } catch (final InterruptedException e) { + code = StatusCode.UNKNOWN; + description = "Validator thread interrupted during pool validation."; + } catch (final TimeoutException e) { + code = StatusCode.WARN; + description = String.format("Pool validation timed out. Max wait is %s ms.", this.maxWait); + } catch (final Exception e) { + code = StatusCode.ERROR; + description = e.getMessage(); + } + return new PoolStatus(code, description, getActiveCount(), getIdleCount()); + } + + + /** + * Performs a health check on a the pool. The recommended implementation is to + * obtain a pool resource, validate it, and return it to the pool. + * + * @return Status code describing pool health. + * + * @throws Exception Thrown to indicate a serious problem with pool validation. + */ + protected abstract StatusCode checkPool() throws Exception; + + + /** + * Gets the number of pool resources idle at present. + * + * @return Number of idle pool resources. + */ + protected abstract int getIdleCount(); + + + /** + * Gets the number of pool resources active at present. + * + * @return Number of active pool resources. + */ + protected abstract int getActiveCount(); + + private static class Validator implements Callable { + private final AbstractPoolMonitor monitor; + + /** + * Instantiates a new Validator. + * + * @param monitor the monitor + */ + public Validator(final AbstractPoolMonitor monitor) { + this.monitor = monitor; + } + + @Override + public StatusCode call() throws Exception { + return this.monitor.checkPool(); + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/monitor/CacheStatus.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/CacheStatus.java new file mode 100644 index 000000000000..7d0e405359ef --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/CacheStatus.java @@ -0,0 +1,100 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import com.google.common.collect.ImmutableList; + +import javax.validation.constraints.NotNull; + +/** + * Describes meaningful health metrics on the status of a cache. + * + * @author Marvin S. Addison + * @since 3.5.1 + */ +public class CacheStatus extends Status { + + private final CacheStatistics[] statistics; + + /** + * Creates a new instance describing cache status. + * + * @param code Status code. + * @param description Optional status description. + * @param statistics One or more sets of cache statistics. + */ + public CacheStatus(final StatusCode code, final String description, @NotNull final CacheStatistics... statistics) { + super(code, buildDescription(description, statistics)); + this.statistics = statistics; + } + + + /** + * Creates a new instance when cache statistics are unavailable due to given exception. + * + * @param e Cause of unavailable statistics. + */ + public CacheStatus(final Exception e) { + super(StatusCode.ERROR, + String.format("Error fetching cache status: %s::%s", e.getClass().getSimpleName(), e.getMessage())); + this.statistics = null; + } + + + /** + * Gets the current cache statistics. + * + * @return Cache statistics. + */ + public CacheStatistics[] getStatistics() { + return ImmutableList.copyOf(this.statistics).toArray(new CacheStatistics[this.statistics.length]); + } + + + /** + * Builds the description string for the retrieved statistics. + * + * @param desc the desc + * @param statistics the statistics + * @return the string + */ + private static String buildDescription(final String desc, final CacheStatistics... statistics) { + if (statistics == null || statistics.length == 0) { + return desc; + } + final StringBuilder sb = new StringBuilder(); + if (desc != null) { + sb.append(desc); + if (!desc.endsWith(".")) { + sb.append('.'); + } + sb.append(' '); + } + sb.append("Cache statistics: ["); + int i = 0; + for (final CacheStatistics stats : statistics) { + if (i++ > 0) { + sb.append('|'); + } + stats.toString(sb); + } + sb.append(']'); + return sb.toString(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/monitor/HealthCheckMonitor.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/HealthCheckMonitor.java new file mode 100644 index 000000000000..946278d8be69 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/HealthCheckMonitor.java @@ -0,0 +1,81 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import javax.validation.constraints.NotNull; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Simple health check monitor that reports the overall health as the greatest reported + * {@link StatusCode} of an arbitrary number of individual checks. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +public class HealthCheckMonitor implements Monitor { + /** Individual monitors that comprise health check. */ + @NotNull + private Collection monitors = Collections.emptySet(); + + + /** + * Sets the monitors that comprise the health check. + * + * @param monitors Collection of monitors responsible for observing various aspects of CAS. + */ + public void setMonitors(final Collection monitors) { + this.monitors = monitors; + } + + /** + * {@inheritDoc} + **/ + @Override + public String getName() { + return HealthCheckMonitor.class.getSimpleName(); + } + + /** + * {@inheritDoc} + **/ + @Override + public HealthStatus observe() { + final Map results = new LinkedHashMap<>(this.monitors.size()); + StatusCode code = StatusCode.UNKNOWN; + Status result; + for (final Monitor monitor : this.monitors) { + try { + result = monitor.observe(); + final StatusCode resCode = result.getCode(); + if (resCode.value() > code.value()) { + code = resCode; + } + } catch (final Exception e) { + code = StatusCode.ERROR; + result = new Status(code, e.getClass().getSimpleName() + ": " + e.getMessage()); + } + results.put(monitor.getName(), result); + } + + return new HealthStatus(code, results); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/monitor/HealthStatus.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/HealthStatus.java new file mode 100644 index 000000000000..f757534f0179 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/HealthStatus.java @@ -0,0 +1,57 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import java.util.Collections; +import java.util.Map; + +/** + * Describes the overall health status of the CAS server as determined by composite status values. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +public class HealthStatus extends Status { + /** Map of names (e.g. monitor that produced it) to status information. */ + private final Map details; + + + /** + * Creates a new status object with the given code. + * + * @param code Status code. + * @param detailMap Map of names to status information. A reasonable name would be, for example, the name of + * the monitor that produced it. + * @see #getCode() + */ + public HealthStatus(final StatusCode code, final Map detailMap) { + super(code); + this.details = Collections.unmodifiableMap(detailMap); + } + + + /** + * Gets the status details comprising the individual health checks performed for overall health status. + * + * @return Map of named status items to status information for each check performed. + */ + public Map getDetails() { + return this.details; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/monitor/MemoryMonitor.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/MemoryMonitor.java new file mode 100644 index 000000000000..c546bb5f7804 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/MemoryMonitor.java @@ -0,0 +1,74 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +/** + * Monitors JVM memory usage. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +public class MemoryMonitor implements Monitor { + + /** Default percent free memory warning threshold. */ + public static final int DEFAULT_FREE_MEMORY_WARN_THRESHOLD = 10; + private static final int PERCENTAGE_VALUE = 100; + + /** Percent free memory warning threshold. */ + private long freeMemoryWarnThreshold = DEFAULT_FREE_MEMORY_WARN_THRESHOLD; + + + /** + * Sets the percent of free memory below which a warning is reported. + * + * @param threshold Percent free memory warning threshold. + */ + public void setFreeMemoryWarnThreshold(final long threshold) { + if (threshold < 0) { + throw new IllegalArgumentException("Warning threshold must be non-negative."); + } + this.freeMemoryWarnThreshold = threshold; + } + + + /** + * {@inheritDoc} + */ + @Override + public String getName() { + return MemoryMonitor.class.getSimpleName(); + } + + + /** + * {@inheritDoc} + */ + @Override + public MemoryStatus observe() { + final StatusCode code; + final long free = Runtime.getRuntime().freeMemory(); + final long total = Runtime.getRuntime().totalMemory(); + if (free * PERCENTAGE_VALUE / total < this.freeMemoryWarnThreshold) { + code = StatusCode.WARN; + } else { + code = StatusCode.OK; + } + return new MemoryStatus(code, free, total); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/monitor/MemoryStatus.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/MemoryStatus.java new file mode 100644 index 000000000000..7a26dc2b8b7a --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/MemoryStatus.java @@ -0,0 +1,71 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +/** + * Describes the memory status of the JVM. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +public class MemoryStatus extends Status { + + private static final double BYTES_PER_MB = 1048510.0; + + /** JVM free memory. */ + private final long freeMemory; + + /** JVM total memory. */ + private final long totalMemory; + + + /** + * Creates a new status object with the given code. + * + * @param code Status code. + * @param free JVM free memory in bytes. + * @param total JVM total memory in bytes. + * + * @see #getCode() + */ + public MemoryStatus(final StatusCode code, final long free, final long total) { + super(code, String.format("%.2fMB free, %.2fMB total.", free / BYTES_PER_MB, total / BYTES_PER_MB)); + this.freeMemory = free; + this.totalMemory = total; + } + + /** + * Gets JVM free memory. + * + * @return Free memory in bytes. + */ + public long getFreeMemory() { + return this.freeMemory; + } + + + /** + * Gets JVM total memory. + * + * @return Max memory in bytes. + */ + public long getTotalMemory() { + return this.totalMemory; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/monitor/PoolStatus.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/PoolStatus.java new file mode 100644 index 000000000000..7858aeb46d43 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/PoolStatus.java @@ -0,0 +1,105 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +/** + * Describes the status of a resource pool. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +public class PoolStatus extends Status { + /** + * Return value for {@link #getActiveCount()} and {@link #getIdleCount()} + * when pool metrics are unknown or unknowable. + */ + public static final int UNKNOWN_COUNT = -1; + + /** Number of idle pool resources. */ + private final int idleCount; + + /** Number of active pool resources. */ + private final int activeCount; + + + /** + * Creates a new status object with the given code. + * + * @param code Status code. + * @param desc Human-readable status description. + * @param active number of active pool resources + * @param idle number of idle pool resources + * @see #getCode() + */ + public PoolStatus(final StatusCode code, final String desc, final int active, final int idle) { + super(code, buildDescription(desc, active, idle)); + this.activeCount = active; + this.idleCount = idle; + } + + + /** + * Gets the number of idle pool resources. + * + * @return Number of idle pool members. + */ + public int getIdleCount() { + return this.idleCount; + } + + + /** + * Gets the number of active pool resources. + * + * @return Number of active pool members. + */ + public int getActiveCount() { + return this.activeCount; + } + + + /** + * Builds the description for the pool. + * + * @param desc the desc + * @param active the active + * @param idle the idle + * @return the string + */ + private static String buildDescription(final String desc, final int active, final int idle) { + final StringBuilder sb = new StringBuilder(); + if (desc != null) { + sb.append(desc); + if (!desc.endsWith(".")) { + sb.append('.'); + } + sb.append(' '); + } + if (active != UNKNOWN_COUNT) { + sb.append(active).append(" active"); + } + if (idle != UNKNOWN_COUNT) { + sb.append(", ").append(idle).append(" idle."); + } + if (sb.length() > 0) { + return sb.toString(); + } + return null; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/monitor/SessionMonitor.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/SessionMonitor.java new file mode 100644 index 000000000000..b6ea3e4f4067 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/SessionMonitor.java @@ -0,0 +1,120 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import javax.validation.constraints.NotNull; + +/** + * Monitors the status of a {@link org.jasig.cas.ticket.registry.TicketRegistry} + * that supports the {@link TicketRegistryState} interface for exposing internal + * state information used in status reports. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +public class SessionMonitor implements Monitor { + /** Ticket registry instance that exposes state info. */ + @NotNull + private TicketRegistryState registryState; + + /** Threshold above which warnings are issued for session count. */ + private int sessionCountWarnThreshold = -1; + + /** Threshold above which warnings are issued for service ticket count. */ + private int serviceTicketCountWarnThreshold = -1; + + + /** + * Sets the ticket registry that exposes state information that may be queried by this monitor. + * @param state the ticket registry state instance + */ + public void setTicketRegistry(final TicketRegistryState state) { + this.registryState = state; + } + + + /** + * Sets the threshold above which warnings are issued for session counts in excess of value. + * + * @param threshold Warn threshold if non-negative value, otherwise warnings are disabled. + */ + public void setSessionCountWarnThreshold(final int threshold) { + this.sessionCountWarnThreshold = threshold; + } + + + /** + * Sets the threshold above which warnings are issued for service ticket counts in excess of value. + * + * @param threshold Warn threshold if non-negative value, otherwise warnings are disabled. + */ + public void setServiceTicketCountWarnThreshold(final int threshold) { + this.serviceTicketCountWarnThreshold = threshold; + } + + + /** + * {@inheritDoc} + */ + @Override + public String getName() { + return SessionMonitor.class.getSimpleName(); + } + + + /** + * {@inheritDoc} + */ + @Override + public SessionStatus observe() { + try { + final int sessionCount = this.registryState.sessionCount(); + final int ticketCount = this.registryState.serviceTicketCount(); + + if (sessionCount == Integer.MIN_VALUE || ticketCount == Integer.MIN_VALUE) { + return new SessionStatus(StatusCode.UNKNOWN, + String.format("Ticket registry %s reports unknown session and/or ticket counts.", + this.registryState.getClass().getName()), + sessionCount, ticketCount); + } + + final StringBuilder msg = new StringBuilder(); + StatusCode code = StatusCode.OK; + if (this.sessionCountWarnThreshold > -1 && sessionCount > this.sessionCountWarnThreshold) { + code = StatusCode.WARN; + msg.append(String.format( + "Session count (%s) is above threshold %s. ", sessionCount, this.sessionCountWarnThreshold)); + } else { + msg.append(sessionCount).append(" sessions. "); + } + if (this.serviceTicketCountWarnThreshold > -1 && ticketCount > this.serviceTicketCountWarnThreshold) { + code = StatusCode.WARN; + msg.append(String.format( + "Service ticket count (%s) is above threshold %s.", + ticketCount, + this.serviceTicketCountWarnThreshold)); + } else { + msg.append(ticketCount).append(" service tickets."); + } + return new SessionStatus(code, msg.toString(), sessionCount, ticketCount); + } catch (final Exception e) { + return new SessionStatus(StatusCode.ERROR, e.getMessage()); + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/monitor/SessionStatus.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/SessionStatus.java new file mode 100644 index 000000000000..72597e2f79be --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/SessionStatus.java @@ -0,0 +1,82 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +/** + * Provides status information about the number of SSO sessions established in CAS. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +public class SessionStatus extends Status { + /** Total number of SSO sessions maintained by CAS. */ + private final int sessionCount; + + /** Total number of service tickets in CAS ticket registry. */ + private final int serviceTicketCount; + + /** + * Creates a new status object with the given code. + * + * @param code Status code. + * @param desc Human-readable status description. + * + * @see #getCode() + */ + public SessionStatus(final StatusCode code, final String desc) { + this(code, desc, 0, 0); + } + + + /** + * Creates a new status object with the given code. + * + * @param code Status code. + * @param desc Human-readable status description. + * @param sessions Number of established SSO sessions in ticket registry. + * @param serviceTickets Number of service tickets in ticket registry. + * + * @see #getCode() + */ + public SessionStatus(final StatusCode code, final String desc, final int sessions, final int serviceTickets) { + super(code, desc); + this.sessionCount = sessions; + this.serviceTicketCount = serviceTickets; + } + + + /** + * Gets total number of SSO sessions maintained by CAS. + * + * @return Total number of SSO sessions. + */ + public int getSessionCount() { + return this.sessionCount; + } + + + /** + * Gets the total number of service tickets in the CAS ticket registry. + * + * @return Total number of service tickets. + */ + public int getServiceTicketCount() { + return this.serviceTicketCount; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/monitor/SimpleCacheStatistics.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/SimpleCacheStatistics.java new file mode 100644 index 000000000000..6c1201338d4c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/SimpleCacheStatistics.java @@ -0,0 +1,118 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import java.util.Formatter; + +/** + * Simple implementation of cache statistics. + * + * @author Marvin S. Addison + * @since 3.5.1 + */ +public class SimpleCacheStatistics implements CacheStatistics { + + private static final double BYTES_PER_MB = 1048510.0; + private static final int PERCENTAGE_VALUE = 100; + + private final long size; + + private final long capacity; + + private final long evictions; + + private String name; + + + /** + * Creates a new instance with given parameters. + * + * @param size Current cache size (e.g. items, bytes, etc). + * @param capacity Current cache capacity (e.g. items, bytes, etc). The units of capacity must be equal to size + * in order to produce a meaningful value for {@link #getPercentFree}. + * @param evictions Number of evictions reported by cache. + */ + public SimpleCacheStatistics(final long size, final long capacity, final long evictions) { + this.size = size; + this.capacity = capacity; + this.evictions = evictions; + } + + + /** + * Creates a new named instance with given parameters. + * + * @param size Current cache size (e.g. items, bytes, etc). + * @param capacity Current cache capacity (e.g. items, bytes, etc). The units of capacity must be equal to size + * in order to produce a meaningful value for {@link #getPercentFree}. + * @param evictions Number of evictions reported by cache. + * @param name Name of cache instance to which statistics apply. + */ + public SimpleCacheStatistics(final long size, final long capacity, final long evictions, final String name) { + this.size = size; + this.capacity = capacity; + this.evictions = evictions; + this.name = name; + } + + public long getSize() { + return this.size; + } + + + public long getCapacity() { + return this.capacity; + } + + + public long getEvictions() { + return this.evictions; + } + + @Override + public int getPercentFree() { + if (this.capacity == 0) { + return 0; + } + return (int) ((this.capacity - this.size) * PERCENTAGE_VALUE / this.capacity); + } + + @Override + public void toString(final StringBuilder builder) { + if (this.name != null) { + builder.append(this.name).append(':'); + } + try (final Formatter formatter = new Formatter(builder)) { + formatter.format("%.2f", this.size / BYTES_PER_MB); + builder.append("MB used, "); + builder.append(getPercentFree()).append("% free, "); + builder.append(this.evictions).append(" evictions"); + } + } + + + /** + * Gets a descriptive name of the cache instance for which statistics apply. + * + * @return Name of cache instance/host to which statistics apply. + */ + public String getName() { + return this.name; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/package.html b/cas-server-core/src/main/java/org/jasig/cas/package.html new file mode 100644 index 000000000000..37fe5fdf5699 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/package.html @@ -0,0 +1,60 @@ + + + + +

This is the entry point to the part of the CAS processing that is independent +of the user/program interface. This layer is mostly a service to create, manage, +validate, query, and destroy tickets. The caller of this layer may be a Web +application running in a Servlet container, but it may also be a Web Service +client, RMI client, or even a program that finds these services compelling.

+

One first enters this layer with opaque Credentials requesting creation of a +TGT. The Credentials are "opaque" because they are an object whose nature is +irrelevant to CAS. The Credentials are carried through the layers until they can +be presented to plugin configuration beans that may recognize the underlying +type and process it. Simple Credentials might be an object with a userid and +password, but they may also be an X.509 Certificate, Kerberos Ticket, Shibboleth +artifact, XML SOAP header, or any other object.

+

The Credentials are presented to a set of objects plugged into the +Authentication process by the system administrators. If one of these plugin +elements recognizes the Credentials, validates their integrity, and maps them to +the identity of a user in the local system, then CAS has logged someone on and +creates a TGT.

+

The results of the login are somewhat opaque. The TGT references an +Authentication object that references a Principal. Minimally the Principal +contains a simple ID string. What else the Principal or Authentication object +contain are transparent to CAS. These objects must be Serializable, because the +Tickets and everything they reference may need to be checkpointed to disk or +shared with multiple machines in a clustering configuration. CAS is managing the +TGT and, as a result, it also saves everything in the concrete classes +referenced by it.

+

Any additional information about the User fetched at login is of no direct +interest to CAS. It may, however, be meaningful to the caller of this layer. In +the case of an HTTP Servlet interface, this would be the View layer that +generates, among other things, the response to the Ticket Validation query.

+

Having created a TGT, CAS then proceeds to create Service Tickets which are +chained off the TGT, and in the case of Proxy authentication creates chains of +TGTs for the Service Ticket. TGTs and STs are stored in a cache until they +expire or are deleted. Various technologies can be plugged into the back end so +that the Ticket cache is shared among machines or persisted across a reboot.

+ + + diff --git a/cas-server-core/src/main/java/org/jasig/cas/remoting/server/RemoteCentralAuthenticationService.java b/cas-server-core/src/main/java/org/jasig/cas/remoting/server/RemoteCentralAuthenticationService.java new file mode 100644 index 000000000000..6df08c18ac4a --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/remoting/server/RemoteCentralAuthenticationService.java @@ -0,0 +1,199 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.remoting.server; + +import org.apache.commons.collections4.Predicate; +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.AuthenticationException; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.logout.LogoutRequest; +import org.jasig.cas.ticket.TicketException; +import org.jasig.cas.ticket.InvalidTicketException; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.validation.Assertion; +import org.springframework.util.Assert; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.constraints.NotNull; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * Wrapper implementation around a CentralAuthenticationService that + * completes the marshalling of parameters from the web-service layer to the + * service layer. Typically the only thing that is done is to validate the + * parameters (as you would in the web tier) and then delegate to the service + * layer. + *

+ * The following properties are required: + *

+ *
    + *
  • centralAuthenticationService - the service layer we are delegating to.
  • + *
+ * + * @author Scott Battaglia + @deprecated As of 4.1. No longer required. The default implementation can be used + to delegate calls to the service layer from WS. + * @since 3.0.0 + */ +@Deprecated +public final class RemoteCentralAuthenticationService implements CentralAuthenticationService { + + /** The CORE to delegate to. */ + @NotNull + private CentralAuthenticationService centralAuthenticationService; + + /** The validators to check the Credential. */ + @NotNull + private Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + + /** + * {@inheritDoc} + * @throws IllegalArgumentException if the Credentials are null or if given + * invalid credentials. + */ + @Override + public TicketGrantingTicket createTicketGrantingTicket(final Credential... credentials) + throws AuthenticationException, TicketException { + + Assert.notNull(credentials, "credentials cannot be null"); + checkForErrors(credentials); + + return this.centralAuthenticationService.createTicketGrantingTicket(credentials); + } + + /** + * {@inheritDoc} + */ + @Override + public ServiceTicket grantServiceTicket(final String ticketGrantingTicketId, final Service service) + throws TicketException { + return this.centralAuthenticationService.grantServiceTicket(ticketGrantingTicketId, service); + } + + /** + * {@inheritDoc} + */ + @Override + public Collection getTickets(@NotNull final Predicate predicate) { + return this.centralAuthenticationService.getTickets(predicate); + } + + /** + * {@inheritDoc} + * @throws IllegalArgumentException if given invalid credentials + */ + @Override + public ServiceTicket grantServiceTicket( + final String ticketGrantingTicketId, final Service service, final Credential... credentials) + throws AuthenticationException, TicketException { + + checkForErrors(credentials); + + return this.centralAuthenticationService.grantServiceTicket(ticketGrantingTicketId, service, credentials); + } + + /** + * {@inheritDoc} + */ + @Override + public T getTicket(final String ticketId, final Class clazz) + throws InvalidTicketException { + return this.centralAuthenticationService.getTicket(ticketId, clazz); + } + + /** + * {@inheritDoc} + */ + @Override + public Assertion validateServiceTicket(final String serviceTicketId, final Service service) throws TicketException { + return this.centralAuthenticationService.validateServiceTicket(serviceTicketId, service); + } + + /** + * {@inheritDoc} + *

Destroy a TicketGrantingTicket and perform back channel logout. This has the effect of invalidating any + * Ticket that was derived from the TicketGrantingTicket being destroyed. May throw an + * {@link IllegalArgumentException} if the TicketGrantingTicket ID is null. + * + * @param ticketGrantingTicketId the id of the ticket we want to destroy + * @return the logout requests. + */ + @Override + public List destroyTicketGrantingTicket(final String ticketGrantingTicketId) { + return this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId); + } + + /** + * {@inheritDoc} + * @throws IllegalArgumentException if the credentials are invalid. + */ + @Override + public TicketGrantingTicket delegateTicketGrantingTicket(final String serviceTicketId, final Credential... credentials) + throws AuthenticationException, TicketException { + + checkForErrors(credentials); + + return this.centralAuthenticationService.delegateTicketGrantingTicket(serviceTicketId, credentials); + } + + /** + * Check for errors by asking the validator to review each credential. + * + * @param credentials the credentials + */ + private void checkForErrors(final Credential... credentials) { + if (credentials == null) { + return; + } + + for (final Credential c : credentials) { + final Set> errors = this.validator.validate(c); + if (!errors.isEmpty()) { + throw new IllegalArgumentException("Error validating credentials: " + errors.toString()); + } + } + } + + /** + * Set the CentralAuthenticationService. + * + * @param centralAuthenticationService The CentralAuthenticationService to + * set. + */ + public void setCentralAuthenticationService( + final CentralAuthenticationService centralAuthenticationService) { + this.centralAuthenticationService = centralAuthenticationService; + } + + /** + * Set the list of validators. + * + * @param validator The array of validators to use. + */ + public void setValidator(final Validator validator) { + this.validator = validator; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/remoting/server/package.html b/cas-server-core/src/main/java/org/jasig/cas/remoting/server/package.html new file mode 100644 index 000000000000..be4da329fe6d --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/remoting/server/package.html @@ -0,0 +1,27 @@ + + + + +Classes to allow CAS to be exposed as a server. + + + diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/AbstractAttributeReleasePolicy.java b/cas-server-core/src/main/java/org/jasig/cas/services/AbstractAttributeReleasePolicy.java new file mode 100644 index 000000000000..7197003c8d29 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/AbstractAttributeReleasePolicy.java @@ -0,0 +1,137 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.jasig.cas.authentication.principal.DefaultPrincipalAttributesRepository; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.PrincipalAttributesRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +/** + * Abstract release policy for attributes, provides common shared settings such as loggers and attribute filter config. + * Subclasses are to provide the behavior for attribute retrieval. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public abstract class AbstractAttributeReleasePolicy implements AttributeReleasePolicy { + + private static final long serialVersionUID = 5325460875620586503L; + + /** The logger. */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** The attribute filter. */ + private AttributeFilter attributeFilter; + + /** Attribute repository that refreshes attributes for a principal. **/ + private PrincipalAttributesRepository principalAttributesRepository = new DefaultPrincipalAttributesRepository(); + + /** Authorize the release of credential for this service. Default is false. **/ + private boolean authorizedToReleaseCredentialPassword; + + /** Authorize the release of PGT for this service. Default is false. **/ + private boolean authorizedToReleaseProxyGrantingTicket; + + @Override + public final void setAttributeFilter(final AttributeFilter filter) { + this.attributeFilter = filter; + } + + public final void setPrincipalAttributesRepository(final PrincipalAttributesRepository repository) { + this.principalAttributesRepository = repository; + } + + protected PrincipalAttributesRepository getPrincipalAttributesRepository() { + return principalAttributesRepository; + } + + /** + * Gets the attribute filter. + * + * @return the attribute filter + */ + protected final AttributeFilter getAttributeFilter() { + return this.attributeFilter; + } + + @Override + public boolean isAuthorizedToReleaseCredentialPassword() { + return this.authorizedToReleaseCredentialPassword; + } + + @Override + public boolean isAuthorizedToReleaseProxyGrantingTicket() { + return this.authorizedToReleaseProxyGrantingTicket; + } + + public void setAuthorizedToReleaseCredentialPassword(final boolean authorizedToReleaseCredentialPassword) { + this.authorizedToReleaseCredentialPassword = authorizedToReleaseCredentialPassword; + } + + public void setAuthorizedToReleaseProxyGrantingTicket(final boolean authorizedToReleaseProxyGrantingTicket) { + this.authorizedToReleaseProxyGrantingTicket = authorizedToReleaseProxyGrantingTicket; + } + + @Override + public final Map getAttributes(final Principal p) { + final Map principalAttributes = this.principalAttributesRepository.getAttributes(p); + final Map attributesToRelease = getAttributesInternal(principalAttributes); + + if (this.attributeFilter != null) { + return this.attributeFilter.filter(attributesToRelease); + } + return attributesToRelease; + } + + /** + * Gets the attributes internally from the implementation. + * + * @param attributes the principal attributes + * @return the attributes allowed for release + */ + protected abstract Map getAttributesInternal(final Map attributes); + + @Override + public int hashCode() { + return new HashCodeBuilder(13, 133).append(this.attributeFilter).toHashCode(); + } + + @Override + public boolean equals(final Object o) { + if (o == null) { + return false; + } + + if (this == o) { + return true; + } + + if (!(o instanceof AbstractAttributeReleasePolicy)) { + return false; + } + + final AbstractAttributeReleasePolicy that = (AbstractAttributeReleasePolicy) o; + return new EqualsBuilder().append(this.attributeFilter, that.attributeFilter).isEquals(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/AbstractRegisteredService.java b/cas-server-core/src/main/java/org/jasig/cas/services/AbstractRegisteredService.java new file mode 100644 index 000000000000..e961158134b1 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/AbstractRegisteredService.java @@ -0,0 +1,451 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.apache.commons.lang3.builder.CompareToBuilder; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.persistence.Column; +import javax.persistence.DiscriminatorColumn; +import javax.persistence.DiscriminatorType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.Lob; +import javax.persistence.PostLoad; +import javax.persistence.Table; +import javax.persistence.Transient; +import javax.validation.constraints.NotNull; +import java.net.URL; +import java.util.HashSet; +import java.util.Set; + +/** + * Base class for mutable, persistable registered services. + * + * @author Marvin S. Addison + * @author Scott Battaglia + * @author Misagh Moayyed + * @since 3.0.0 + */ +@Entity +@Inheritance +@DiscriminatorColumn(name = "expression_type", length = 15, discriminatorType = DiscriminatorType.STRING, + columnDefinition = "VARCHAR(15) DEFAULT 'ant'") +@Table(name = "RegisteredServiceImpl") +public abstract class AbstractRegisteredService implements RegisteredService, Comparable { + + private static final long serialVersionUID = 7645279151115635245L; + + /** The logger instance. */ + @Transient + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** The unique identifier for this service. */ + @Column(length = 255, updatable = true, insertable = true, nullable = false) + protected String serviceId; + + @Column(length = 255, updatable = true, insertable = true, nullable = false) + private String name; + + @Column(length = 255, updatable = true, insertable = true, nullable = true) + private String theme; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long id = RegisteredService.INITIAL_IDENTIFIER_VALUE; + + @Column(length = 255, updatable = true, insertable = true, nullable = false) + private String description; + + /** + * Proxy policy for the service. + * By default, the policy is {@link RefuseRegisteredServiceProxyPolicy}. + */ + @Lob + @Column(name = "proxy_policy", nullable = true) + private RegisteredServiceProxyPolicy proxyPolicy = new RefuseRegisteredServiceProxyPolicy(); + + @Column(name = "evaluation_order", nullable = false) + private int evaluationOrder; + + /** + * Resolve the username for this service. + * By default the resolver is {@link DefaultRegisteredServiceUsernameProvider}. + */ + @Lob + @Column(name = "username_attr", nullable = true) + private RegisteredServiceUsernameAttributeProvider usernameAttributeProvider = + new DefaultRegisteredServiceUsernameProvider(); + + /** + * The logout type of the service. + * The default logout type is the back channel one. + */ + @Column(name = "logout_type", nullable = true) + private LogoutType logoutType = LogoutType.BACK_CHANNEL; + + @Lob + @Column(name = "required_handlers") + private HashSet requiredHandlers = new HashSet<>(); + + /** The attribute filtering policy. */ + @Lob + @Column(name = "attribute_release", nullable = true) + private AttributeReleasePolicy attributeReleasePolicy = new ReturnAllowedAttributeReleasePolicy(); + + @Column(name = "logo") + private URL logo; + + @Column(name = "logout_url") + private URL logoutUrl; + + @Lob + @Column(name = "access_strategy", nullable = true) + private RegisteredServiceAccessStrategy accessStrategy = + new DefaultRegisteredServiceAccessStrategy(); + + @Lob + @Column(name = "public_key", nullable = true) + private RegisteredServicePublicKey publicKey; + + @Override + public long getId() { + return this.id; + } + + @Override + public String getDescription() { + return this.description; + } + + @Override + public String getServiceId() { + return this.serviceId; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getTheme() { + return this.theme; + } + + @Override + public RegisteredServiceProxyPolicy getProxyPolicy() { + return this.proxyPolicy; + } + + @Override + public RegisteredServiceAccessStrategy getAccessStrategy() { + return this.accessStrategy; + } + + @Override + public URL getLogoutUrl() { + return this.logoutUrl; + } + + /** + * Initializes the registered service with default values + * for fields that are unspecified. Only triggered by JPA. + * @since 4.1 + */ + @PostLoad + public final void postLoad() { + if (this.proxyPolicy == null) { + this.proxyPolicy = new RefuseRegisteredServiceProxyPolicy(); + } + if (this.usernameAttributeProvider == null) { + this.usernameAttributeProvider = new DefaultRegisteredServiceUsernameProvider(); + } + if (this.logoutType == null) { + this.logoutType = LogoutType.BACK_CHANNEL; + } + if (this.requiredHandlers == null) { + this.requiredHandlers = new HashSet<>(); + } + if (this.accessStrategy == null) { + this.accessStrategy = new DefaultRegisteredServiceAccessStrategy(); + } + if (this.attributeReleasePolicy == null) { + this.attributeReleasePolicy = new ReturnAllowedAttributeReleasePolicy(); + } + } + + @Override + public boolean equals(final Object o) { + if (o == null) { + return false; + } + + if (this == o) { + return true; + } + + if (!(o instanceof AbstractRegisteredService)) { + return false; + } + + final AbstractRegisteredService that = (AbstractRegisteredService) o; + + return new EqualsBuilder() + .append(this.proxyPolicy, that.proxyPolicy) + .append(this.evaluationOrder, that.evaluationOrder) + .append(this.description, that.description) + .append(this.name, that.name) + .append(this.serviceId, that.serviceId) + .append(this.theme, that.theme) + .append(this.usernameAttributeProvider, that.usernameAttributeProvider) + .append(this.logoutType, that.logoutType) + .append(this.attributeReleasePolicy, that.attributeReleasePolicy) + .append(this.accessStrategy, that.accessStrategy) + .append(this.logo, that.logo) + .append(this.publicKey, that.publicKey) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(7, 31) + .append(this.description) + .append(this.serviceId) + .append(this.name) + .append(this.theme) + .append(this.evaluationOrder) + .append(this.usernameAttributeProvider) + .append(this.accessStrategy) + .append(this.logoutType) + .append(this.attributeReleasePolicy) + .append(this.accessStrategy) + .append(this.logo) + .append(this.publicKey) + .toHashCode(); + } + + public void setProxyPolicy(final RegisteredServiceProxyPolicy policy) { + this.proxyPolicy = policy; + } + + public void setDescription(final String description) { + this.description = description; + } + + /** + * Sets the service identifier. Extensions are to define the format. + * + * @param id the new service id + */ + public abstract void setServiceId(final String id); + + public void setId(final long id) { + this.id = id; + } + + public void setName(final String name) { + this.name = name; + } + + public void setTheme(final String theme) { + this.theme = theme; + } + + @Override + public void setEvaluationOrder(final int evaluationOrder) { + this.evaluationOrder = evaluationOrder; + } + + @Override + public int getEvaluationOrder() { + return this.evaluationOrder; + } + + @Override + public RegisteredServiceUsernameAttributeProvider getUsernameAttributeProvider() { + return this.usernameAttributeProvider; + } + + public void setAccessStrategy(final RegisteredServiceAccessStrategy accessStrategy) { + this.accessStrategy = accessStrategy; + } + + public void setLogoutUrl(final URL logoutUrl) { + this.logoutUrl = logoutUrl; + } + + /** + * Sets the user attribute provider instance + * when providing usernames to this registered service. + * + * @param usernameProvider the new username attribute + */ + public void setUsernameAttributeProvider(final RegisteredServiceUsernameAttributeProvider usernameProvider) { + this.usernameAttributeProvider = usernameProvider; + } + + @Override + public final LogoutType getLogoutType() { + return logoutType; + } + + /** + * Set the logout type of the service. + * + * @param logoutType the logout type of the service. + */ + public final void setLogoutType(final LogoutType logoutType) { + this.logoutType = logoutType; + } + + @Override + public final RegisteredService clone() { + final AbstractRegisteredService clone = newInstance(); + clone.copyFrom(this); + return clone; + } + + /** + * Copies the properties of the source service into this instance. + * + * @param source Source service from which to copy properties. + */ + public void copyFrom(final RegisteredService source) { + this.setId(source.getId()); + this.setProxyPolicy(source.getProxyPolicy()); + this.setDescription(source.getDescription()); + this.setName(source.getName()); + this.setServiceId(source.getServiceId()); + this.setTheme(source.getTheme()); + this.setEvaluationOrder(source.getEvaluationOrder()); + this.setUsernameAttributeProvider(source.getUsernameAttributeProvider()); + this.setLogoutType(source.getLogoutType()); + this.setAttributeReleasePolicy(source.getAttributeReleasePolicy()); + this.setAccessStrategy(source.getAccessStrategy()); + this.setLogo(source.getLogo()); + this.setPublicKey(source.getPublicKey()); + } + + /** + * {@inheritDoc} + * Compares this instance with the other registered service based on + * evaluation order, name. The name comparison is case insensitive. + * + * @see #getEvaluationOrder() + */ + @Override + public int compareTo(final RegisteredService other) { + return new CompareToBuilder() + .append(this.getEvaluationOrder(), other.getEvaluationOrder()) + .append(this.getName().toLowerCase(), other.getName().toLowerCase()) + .append(this.getServiceId(), other.getServiceId()) + .toComparison(); + } + + @Override + public String toString() { + final ToStringBuilder toStringBuilder = new ToStringBuilder(null, ToStringStyle.SHORT_PREFIX_STYLE); + toStringBuilder.append("id", this.id); + toStringBuilder.append("name", this.name); + toStringBuilder.append("description", this.description); + toStringBuilder.append("serviceId", this.serviceId); + toStringBuilder.append("usernameAttributeProvider", this.usernameAttributeProvider); + toStringBuilder.append("theme", this.theme); + toStringBuilder.append("evaluationOrder", this.evaluationOrder); + toStringBuilder.append("logoutType", this.logoutType); + toStringBuilder.append("attributeReleasePolicy", this.attributeReleasePolicy); + toStringBuilder.append("accessStrategy", this.accessStrategy); + toStringBuilder.append("publicKey", this.publicKey); + toStringBuilder.append("proxyPolicy", this.proxyPolicy); + toStringBuilder.append("logo", this.logo); + + return toStringBuilder.toString(); + } + + /** + * Create a new service instance. + * + * @return the registered service + */ + protected abstract AbstractRegisteredService newInstance(); + + @Override + public Set getRequiredHandlers() { + if (this.requiredHandlers == null) { + this.requiredHandlers = new HashSet<>(); + } + return this.requiredHandlers; + } + + /** + * Sets the required handlers for this service. + * + * @param handlers the new required handlers + */ + public void setRequiredHandlers(final Set handlers) { + getRequiredHandlers().clear(); + if (handlers == null) { + return; + } + for (final String handler : handlers) { + getRequiredHandlers().add(handler); + } + } + + /** + * Sets the attribute filtering policy. + * + * @param policy the new attribute filtering policy + */ + public final void setAttributeReleasePolicy(final AttributeReleasePolicy policy) { + this.attributeReleasePolicy = policy; + } + + @Override + public final AttributeReleasePolicy getAttributeReleasePolicy() { + return this.attributeReleasePolicy; + } + + @Override + public URL getLogo() { + return this.logo; + } + + public void setLogo(final URL logo) { + this.logo = logo; + } + + @Override + public RegisteredServicePublicKey getPublicKey() { + return this.publicKey; + } + + public void setPublicKey(@NotNull final RegisteredServicePublicKey publicKey) { + this.publicKey = publicKey; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/AnonymousRegisteredServiceUsernameAttributeProvider.java b/cas-server-core/src/main/java/org/jasig/cas/services/AnonymousRegisteredServiceUsernameAttributeProvider.java new file mode 100644 index 000000000000..57c09dcb0262 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/AnonymousRegisteredServiceUsernameAttributeProvider.java @@ -0,0 +1,97 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.jasig.cas.authentication.principal.PersistentIdGenerator; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.authentication.principal.ShibbolethCompatiblePersistentIdGenerator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.validation.constraints.NotNull; + +/** + * Generates a persistent id as username for anonymous service access. + * By default, the generation is handled by + * {@link org.jasig.cas.authentication.principal.ShibbolethCompatiblePersistentIdGenerator}. + * Generated ids are unique per service. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class AnonymousRegisteredServiceUsernameAttributeProvider implements RegisteredServiceUsernameAttributeProvider { + + private static final long serialVersionUID = 7050462900237284803L; + + private static final Logger LOGGER = LoggerFactory.getLogger(AnonymousRegisteredServiceUsernameAttributeProvider.class); + + /** Encoder to generate PseudoIds. */ + @NotNull + private final PersistentIdGenerator persistentIdGenerator; + + /** + * Instantiates a new anonymous registered service username attribute provider. + * The default id generator used here is {@link ShibbolethCompatiblePersistentIdGenerator}. + */ + public AnonymousRegisteredServiceUsernameAttributeProvider() { + this(new ShibbolethCompatiblePersistentIdGenerator()); + } + + /** + * Instantiates a new default registered service username provider. + * + * @param persistentIdGenerator the persistent id generator + */ + public AnonymousRegisteredServiceUsernameAttributeProvider(@NotNull final PersistentIdGenerator persistentIdGenerator) { + this.persistentIdGenerator = persistentIdGenerator; + } + + public PersistentIdGenerator getPersistentIdGenerator() { + return this.persistentIdGenerator; + } + + @Override + public String resolveUsername(final Principal principal, final Service service) { + final String id = this.persistentIdGenerator.generate(principal, service); + LOGGER.debug("Resolved username [{}] for anonymous access", id); + return id; + } + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj.getClass() != getClass()) { + return false; + } + final AnonymousRegisteredServiceUsernameAttributeProvider rhs = + (AnonymousRegisteredServiceUsernameAttributeProvider) obj; + return this.persistentIdGenerator.equals(rhs.persistentIdGenerator); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(13, 113).toHashCode(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/DefaultRegisteredServiceAccessStrategy.java b/cas-server-core/src/main/java/org/jasig/cas/services/DefaultRegisteredServiceAccessStrategy.java new file mode 100644 index 000000000000..7516955157b8 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/DefaultRegisteredServiceAccessStrategy.java @@ -0,0 +1,257 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.services; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * This is {@link DefaultRegisteredServiceAccessStrategy} + * that allows the following rules: + * + *

    + *
  • A service may be disallowed to use CAS for authentication
  • + *
  • A service may be disallowed to take part in CAS single sign-on such that + * presentation of credentials would always be required.
  • + *
  • A service may be prohibited from receiving a service ticket + * if the existing principal attributes don't contain the required attributes + * that otherwise grant access to the service.
  • + *
+ * + * @author Misagh Moayyed mmoayyed@unicon.net + * @since 4.1 + */ +public class DefaultRegisteredServiceAccessStrategy implements RegisteredServiceAccessStrategy { + + private static final long serialVersionUID = 1245279151345635245L; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** Is the service allowed at all? **/ + private boolean enabled = true; + + /** Is the service allowed to use SSO? **/ + private boolean ssoEnabled = true; + + /** + * Defines the attribute aggregation behavior when checking for required attributes. + * Default requires that all attributes be present and match the principal's. + */ + private boolean requireAllAttributes = true; + + /** + * Collection of required attributes + * for this service to proceed. + */ + private Map> requiredAttributes = new HashMap<>(); + + /** + * Instantiates a new Default registered service authorization strategy. + * By default, rules indicate that services are both enabled + * and can participate in SSO. + */ + public DefaultRegisteredServiceAccessStrategy() { + this(true, true); + } + + /** + * Instantiates a new Default registered service authorization strategy. + * + * @param enabled the enabled + * @param ssoEnabled the sso enabled + */ + public DefaultRegisteredServiceAccessStrategy(final boolean enabled, final boolean ssoEnabled) { + this.enabled = enabled; + this.ssoEnabled = ssoEnabled; + } + + public final void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + /** + * Set to enable/authorize this service. + * @param ssoEnabled true to enable service + */ + public final void setSsoEnabled(final boolean ssoEnabled) { + this.ssoEnabled = ssoEnabled; + } + + public boolean isEnabled() { + return this.enabled; + } + + public boolean isSsoEnabled() { + return this.ssoEnabled; + } + + /** + * Defines the attribute aggregation when checking for required attributes. + * Default requires that all attributes be present and match the principal's. + * @param requireAllAttributes the require all attributes + */ + public final void setRequireAllAttributes(final boolean requireAllAttributes) { + this.requireAllAttributes = requireAllAttributes; + } + + public final boolean isRequireAllAttributes() { + return this.requireAllAttributes; + } + + public Map> getRequiredAttributes() { + return ImmutableMap.copyOf(this.requiredAttributes); + } + + /** + * Defines the required attribute names and values that + * must be available to the principal before the flow + * can proceed to the next step. Every attribute in + * the map can be linked to multiple values. + * + * @param requiredAttributes the required attributes + */ + public final void setRequiredAttributes(final Map> requiredAttributes) { + this.requiredAttributes = requiredAttributes; + } + + /** + * {@inheritDoc} + * + * Verify presence of service required attributes. + *
    + *
  • If no required attributes are specified, authz is granted.
  • + *
  • If ALL required attributes must be present, and the principal contains all and there is + * at least one attribute value that matches the required, authz is granted.
  • + *
  • If ALL required attributes don't have to be present, and there is at least + * one principal attribute present whose value matches the required, authz is granted.
  • + *
  • Otherwise, access is denied
  • + *
+ * Note that comparison of principal/required attributes is case-sensitive. Exact matches are required + * for any individual attribute value. + */ + @Override + public boolean doPrincipalAttributesAllowServiceAccess(final Map principalAttributes) { + if (this.requiredAttributes.isEmpty()) { + logger.debug("No required attributes are specified"); + return true; + } + if (principalAttributes.isEmpty()) { + logger.debug("No principal attributes are found to satisfy attribute requirements"); + return false; + } + + if (principalAttributes.size() < this.requiredAttributes.size()) { + logger.debug("The size of the principal attributes that are [{}] does not match requirements, " + + "which means the principal is not carrying enough data to grant authorization", + principalAttributes); + return false; + } + + final Map> requiredAttrs = this.getRequiredAttributes(); + logger.debug("These required attributes [{}] are examined against [{}] before service can proceed.", + requiredAttrs, principalAttributes); + + final Sets.SetView difference = Sets.intersection(requiredAttrs.keySet(), principalAttributes.keySet()); + final Set copy = difference.immutableCopy(); + + if (this.requireAllAttributes && copy.size() < this.requiredAttributes.size()) { + logger.debug("Not all required attributes are available to the principal"); + return false; + } + + for (final String key : copy) { + final Set requiredValues = this.requiredAttributes.get(key); + final Set availableValues; + + final Object objVal = principalAttributes.get(key); + if (objVal instanceof Collection) { + final Collection valCol = (Collection) objVal; + availableValues = Sets.newHashSet(valCol.toArray()); + } else { + availableValues = Collections.singleton(objVal); + } + + final Sets.SetView differenceInValues = Sets.intersection(availableValues, requiredValues); + if (differenceInValues.size() > 0) { + logger.info("Principal is authorized to access the service"); + return true; + } + } + logger.info("Principal is denied access as the required attributes for the registered service are missing"); + return false; + } + + @Override + public boolean isServiceAccessAllowedForSso() { + if (!this.ssoEnabled) { + logger.trace("Service is not authorized to participate in SSO."); + } + return this.ssoEnabled; + } + + @Override + public boolean isServiceAccessAllowed() { + if (!this.enabled) { + logger.trace("Service is not enabled in service registry."); + } + return this.enabled; + } + + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj.getClass() != getClass()) { + return false; + } + final DefaultRegisteredServiceAccessStrategy rhs = (DefaultRegisteredServiceAccessStrategy) obj; + return new EqualsBuilder() + .append(this.enabled, rhs.enabled) + .append(this.ssoEnabled, rhs.ssoEnabled) + .append(this.requireAllAttributes, rhs.requireAllAttributes) + .append(this.requiredAttributes, rhs.requiredAttributes) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder() + .append(this.enabled) + .append(this.ssoEnabled) + .append(this.requireAllAttributes) + .append(this.requiredAttributes) + .toHashCode(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/DefaultRegisteredServiceUsernameProvider.java b/cas-server-core/src/main/java/org/jasig/cas/services/DefaultRegisteredServiceUsernameProvider.java new file mode 100644 index 000000000000..e37b40b0c64a --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/DefaultRegisteredServiceUsernameProvider.java @@ -0,0 +1,61 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Resolves the username for the service to be the default principal id. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class DefaultRegisteredServiceUsernameProvider implements RegisteredServiceUsernameAttributeProvider { + private static final long serialVersionUID = 5823989148794052951L; + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public String resolveUsername(final Principal principal, final Service service) { + logger.debug("Returning the default principal id [{}] for username.", principal.getId()); + return principal.getId(); + } + + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj.getClass() != getClass()) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return new HashCodeBuilder(13, 113).toHashCode(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/DefaultServicesManagerImpl.java b/cas-server-core/src/main/java/org/jasig/cas/services/DefaultServicesManagerImpl.java new file mode 100644 index 000000000000..2c98ab576d69 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/DefaultServicesManagerImpl.java @@ -0,0 +1,177 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.jasig.inspektr.audit.annotation.Audit; +import org.jasig.cas.authentication.principal.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.transaction.annotation.Transactional; + +import javax.validation.constraints.NotNull; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Default implementation of the {@link ServicesManager} interface. If there are + * no services registered with the server, it considers the ServicecsManager + * disabled and will not prevent any service from using CAS. + * + * @author Scott Battaglia + * @since 3.1 + */ +public final class DefaultServicesManagerImpl implements ReloadableServicesManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultServicesManagerImpl.class); + + /** Instance of ServiceRegistryDao. */ + @NotNull + private final ServiceRegistryDao serviceRegistryDao; + + /** Map to store all services. */ + private ConcurrentHashMap services = new ConcurrentHashMap<>(); + + /** + * Instantiates a new default services manager impl. + * + * @param serviceRegistryDao the service registry dao + */ + public DefaultServicesManagerImpl(final ServiceRegistryDao serviceRegistryDao) { + this.serviceRegistryDao = serviceRegistryDao; + + load(); + } + + /** + * Constructs an instance of the {@link DefaultServicesManagerImpl} where the default RegisteredService + * can include a set of default attributes to use if no services are defined in the registry. + * + * @deprecated + *

As of 4.1. Use {@link #DefaultServicesManagerImpl(ServiceRegistryDao)} + * instead. The defaultAttributes parameter is no longer used. Attributes are configured + * per service definition in the services registry. See {@link RegisteredService#getAttributeReleasePolicy()} + * for more details.

+ * + * @param serviceRegistryDao the Service Registry Dao. + * @param defaultAttributes the list of default attributes to use. + */ + @Deprecated + public DefaultServicesManagerImpl(final ServiceRegistryDao serviceRegistryDao, + final List defaultAttributes) { + this(serviceRegistryDao); + LOGGER.warn("This constructor is deprecated and will be removed in future CAS versions"); + } + + @Transactional(readOnly = false) + @Audit(action = "DELETE_SERVICE", actionResolverName = "DELETE_SERVICE_ACTION_RESOLVER", + resourceResolverName = "DELETE_SERVICE_RESOURCE_RESOLVER") + @Override + public synchronized RegisteredService delete(final long id) { + final RegisteredService r = findServiceBy(id); + if (r == null) { + return null; + } + + this.serviceRegistryDao.delete(r); + this.services.remove(id); + + return r; + } + + /** + * {@inheritDoc} + */ + @Override + public RegisteredService findServiceBy(final Service service) { + final Collection c = convertToTreeSet(); + + for (final RegisteredService r : c) { + if (r.matches(service)) { + return r; + } + } + + return null; + } + + @Override + public RegisteredService findServiceBy(final long id) { + final RegisteredService r = this.services.get(id); + + try { + return r == null ? null : r.clone(); + } catch (final CloneNotSupportedException e) { + return r; + } + } + + /** + * Stuff services to tree set. + * + * @return the tree set + */ + protected TreeSet convertToTreeSet() { + return new TreeSet(this.services.values()); + } + + public Collection getAllServices() { + return Collections.unmodifiableCollection(convertToTreeSet()); + } + + @Override + public boolean matchesExistingService(final Service service) { + return findServiceBy(service) != null; + } + + @Transactional(readOnly = false) + @Audit(action = "SAVE_SERVICE", actionResolverName = "SAVE_SERVICE_ACTION_RESOLVER", + resourceResolverName = "SAVE_SERVICE_RESOURCE_RESOLVER") + @Override + public synchronized RegisteredService save(final RegisteredService registeredService) { + final RegisteredService r = this.serviceRegistryDao.save(registeredService); + this.services.put(r.getId(), r); + return r; + } + + @Override + public void reload() { + LOGGER.info("Reloading registered services."); + load(); + } + + /** + * Load services that are provided by the DAO. + */ + private void load() { + final ConcurrentHashMap localServices = + new ConcurrentHashMap<>(); + + for (final RegisteredService r : this.serviceRegistryDao.load()) { + LOGGER.debug("Adding registered service {}", r.getServiceId()); + localServices.put(r.getId(), r); + } + + this.services = localServices; + LOGGER.info("Loaded {} services.", this.services.size()); + + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/InMemoryServiceRegistryDaoImpl.java b/cas-server-core/src/main/java/org/jasig/cas/services/InMemoryServiceRegistryDaoImpl.java new file mode 100644 index 000000000000..e9a69739b1b4 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/InMemoryServiceRegistryDaoImpl.java @@ -0,0 +1,105 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.validation.constraints.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * Default In Memory Service Registry Dao for test/demonstration purposes. + * + * @author Scott Battaglia + * @since 3.1 + * + */ +public final class InMemoryServiceRegistryDaoImpl implements ServiceRegistryDao { + + private static final Logger LOGGER = LoggerFactory.getLogger(InMemoryServiceRegistryDaoImpl.class); + + @NotNull + private List registeredServices = new ArrayList<>(); + + /** + * Instantiates a new In memory service registry. + */ + public InMemoryServiceRegistryDaoImpl() { + LOGGER.warn("Runtime memory is used as the persistence storage for retrieving and persisting service definitions. " + + "Changes that are made to service definitions during runtime " + + "will be LOST upon container restarts."); + } + + @Override + public boolean delete(final RegisteredService registeredService) { + return this.registeredServices.remove(registeredService); + } + + @Override + public RegisteredService findServiceById(final long id) { + for (final RegisteredService r : this.registeredServices) { + if (r.getId() == id) { + return r; + } + } + + return null; + } + + @Override + public List load() { + return this.registeredServices; + } + + @Override + public RegisteredService save(final RegisteredService registeredService) { + if (registeredService.getId() == RegisteredService.INITIAL_IDENTIFIER_VALUE) { + ((AbstractRegisteredService) registeredService).setId(findHighestId()+1); + } + + this.registeredServices.remove(registeredService); + this.registeredServices.add(registeredService); + + return registeredService; + } + + public void setRegisteredServices(final List registeredServices) { + this.registeredServices = registeredServices; + } + + /** + * This isn't super-fast but we don't expect thousands of services. + * + * @return the highest service id in the list of registered services + */ + private long findHighestId() { + long id = 0; + + for (final RegisteredService r : this.registeredServices) { + if (r.getId() > id) { + id = r.getId(); + } + } + + return id; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/JsonServiceRegistryConfigWatcher.java b/cas-server-core/src/main/java/org/jasig/cas/services/JsonServiceRegistryConfigWatcher.java new file mode 100644 index 000000000000..b879f2def9d8 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/JsonServiceRegistryConfigWatcher.java @@ -0,0 +1,192 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.services; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import static java.nio.file.StandardWatchEventKinds.*; + +/** + * This is {@link JsonServiceRegistryConfigWatcher} that watches the json config directory + * for changes and promptly attempts to reload the CAS service registry configuration. + * + * @author Misagh Moayyed + * @since 4.1.0 + */ +class JsonServiceRegistryConfigWatcher implements Runnable { + private static final Logger LOGGER = LoggerFactory.getLogger(JsonServiceRegistryConfigWatcher.class); + + private final AtomicBoolean running = new AtomicBoolean(false); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final Lock readLock = this.lock.readLock(); + + private final WatchService watcher; + + private final JsonServiceRegistryDao serviceRegistryDao; + + /** + * Instantiates a new Json service registry config watcher. + * + * @param serviceRegistryDao the registry to callback + */ + public JsonServiceRegistryConfigWatcher(final JsonServiceRegistryDao serviceRegistryDao) { + try { + this.serviceRegistryDao = serviceRegistryDao; + this.watcher = FileSystems.getDefault().newWatchService(); + final WatchEvent.Kind[] kinds = (WatchEvent.Kind[]) + Arrays.asList(ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY).toArray(); + this.serviceRegistryDao.getServiceRegistryDirectory().register(this.watcher, kinds); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void run() { + if (running.compareAndSet(false, true)) { + while (running.get()) { + // wait for key to be signaled + WatchKey key = null; + try { + key = watcher.take(); + handleEvent(key); + } catch (final InterruptedException e) { + return; + } finally { + /* + Reset the key -- this step is critical to receive + further watch events. If the key is no longer valid, the directory + is inaccessible so exit the loop. + */ + final boolean valid = (key != null && key.reset()); + if (!valid) { + LOGGER.warn("Directory key is no longer valid. Quitting watcher service"); + break; + } + } + } + } + + } + + /** + * Handle event. + * + * @param key the key + */ + private void handleEvent(final WatchKey key) { + this.readLock.lock(); + try { + for (final WatchEvent event : key.pollEvents()) { + if (event.count() <= 1) { + final WatchEvent.Kind kind = event.kind(); + + //The filename is the context of the event. + final WatchEvent ev = (WatchEvent) event; + final Path filename = ev.context(); + + final Path parent = (Path) key.watchable(); + final Path fullPath = parent.resolve(filename); + final File file = fullPath.toFile(); + + LOGGER.trace("Detected event [{}] on file [{}]. Loading change...", kind, file); + if (kind.name().equals(ENTRY_CREATE.name()) && file.exists()) { + handleCreateEvent(file); + } else if (kind.name().equals(ENTRY_DELETE.name())) { + handleDeleteEvent(); + } else if (kind.name().equals(ENTRY_MODIFY.name()) && file.exists()) { + handleModifyEvent(file); + } + } + + } + } finally { + this.readLock.unlock(); + } + } + + /** + * Handle modify event. + * + * @param file the file + */ + private void handleModifyEvent(final File file) { + /* + load the entry and save it back to the map + without any warnings on duplicate ids. + */ + final RegisteredService newService = this.serviceRegistryDao.loadRegisteredServiceFromFile(file); + if (newService == null) { + LOGGER.warn("New service definition could not be loaded from [{}]", file.getAbsolutePath()); + } else { + final RegisteredService oldService = this.serviceRegistryDao.findServiceById(newService.getId()); + + if (!newService.equals(oldService)) { + this.serviceRegistryDao.updateRegisteredService(newService); + this.serviceRegistryDao.refreshServicesManager(); + } + } + } + + /** + * Handle delete event. + */ + private void handleDeleteEvent() { + this.serviceRegistryDao.load(); + this.serviceRegistryDao.refreshServicesManager(); + } + + /** + * Handle create event. + * + * @param file the file + */ + private void handleCreateEvent(final File file) { + //load the entry and add it to the map + final RegisteredService service = this.serviceRegistryDao.loadRegisteredServiceFromFile(file); + if (service == null) { + LOGGER.warn("No service definition was loaded from [{}]", file); + return; + } + if (this.serviceRegistryDao.findServiceById(service.getId()) != null) { + LOGGER.warn("Found a service definition [{}] with a duplicate id [{}] in [{}]. " + + "This will overwrite previous service definitions and is likely a " + + "configuration problem. Make sure all services have a unique id and try again.", + service.getServiceId(), service.getId(), file.getAbsolutePath()); + + } + this.serviceRegistryDao.updateRegisteredService(service); + this.serviceRegistryDao.refreshServicesManager(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/JsonServiceRegistryDao.java b/cas-server-core/src/main/java/org/jasig/cas/services/JsonServiceRegistryDao.java new file mode 100644 index 000000000000..4f86d59e701e --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/JsonServiceRegistryDao.java @@ -0,0 +1,306 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.util.JsonSerializer; +import org.jasig.cas.util.LockedOutputStream; +import org.jasig.cas.util.services.RegisteredServiceJsonSerializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.util.Assert; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Implementation of ServiceRegistryDao that reads services definition from JSON + * configuration file at the Spring Application Context initialization time. JSON files are + * expected to be found inside a directory location and this DAO will recursively look through + * the directory structure to find relevant JSON files. Files are expected to have the + * {@value #FILE_EXTENSION} extension. An example of the JSON file is included here: + * + *
+ {
+     "@class" : "org.jasig.cas.services.RegexRegisteredService",
+     "id" : 103935657744185,
+     "description" : "This is the application description",
+     "serviceId" : "https://app.school.edu",
+     "name" : "testSaveAttributeReleasePolicyAllowedAttrRulesAndFilter",
+     "theme" : "testtheme",
+     "proxyPolicy" : {
+        "@class" : "org.jasig.cas.services.RegexMatchingRegisteredServiceProxyPolicy",
+        "pattern" : "https://.+"
+     },
+     "enabled" : true,
+     "ssoEnabled" : false,
+     "evaluationOrder" : 1000,
+     "usernameAttributeProvider" : {
+        "@class" : "org.jasig.cas.services.DefaultRegisteredServiceUsernameProvider"
+     },
+     "logoutType" : "BACK_CHANNEL",
+     "requiredHandlers" : [ "java.util.HashSet", [ "handler1", "handler2" ] ],
+     "attributeReleasePolicy" : {
+        "@class" : "org.jasig.cas.services.ReturnAllowedAttributeReleasePolicy",
+        "attributeFilter" : {
+            "@class" : "org.jasig.cas.services.support.RegisteredServiceRegexAttributeFilter",
+            "pattern" : "\\w+"
+        },
+        "allowedAttributes" : [ "java.util.ArrayList", [ "uid", "sn", "cn" ] ]
+     }
+ }
+ * 
+ * + * @author Dmitriy Kopylenko + * @author Marvin S. Addison + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class JsonServiceRegistryDao implements ServiceRegistryDao, ApplicationContextAware { + + private static final Logger LOGGER = LoggerFactory.getLogger(JsonServiceRegistryDao.class); + + /** + * File extension of registered service JSON files. + */ + private static final String FILE_EXTENSION = "json"; + + /** + * Map of service ID to registered service. + */ + private Map serviceMap = new ConcurrentHashMap<>(); + + /** + * The Service registry directory. + */ + private final Path serviceRegistryDirectory; + + /** + * The Registered service json serializer. + */ + private final JsonSerializer registeredServiceJsonSerializer; + + private ApplicationContext applicationContext; + + /** + * Instantiates a new Json service registry dao. + * + * @param configDirectory the config directory + * @param registeredServiceJsonSerializer the registered service json serializer + */ + public JsonServiceRegistryDao(final Path configDirectory, final JsonSerializer registeredServiceJsonSerializer) { + this.serviceRegistryDirectory = configDirectory; + Assert.isTrue(this.serviceRegistryDirectory.toFile().exists(), serviceRegistryDirectory + " does not exist"); + Assert.isTrue(this.serviceRegistryDirectory.toFile().isDirectory(), serviceRegistryDirectory + " is not a directory"); + this.registeredServiceJsonSerializer = registeredServiceJsonSerializer; + initializeWatchServiceThread(); + } + + /** + * Instantiates a new Json service registry dao. + * Sets the path to the directory where JSON service registry entries are + * stored. Uses the {@link RegisteredServiceJsonSerializer} by default. + * + * @param configDirectory the config directory where service registry files can be found. + */ + public JsonServiceRegistryDao(final Path configDirectory) { + this(configDirectory, new RegisteredServiceJsonSerializer()); + } + + /** + * Instantiates a new Json service registry dao. + * Sets the path to the directory where JSON service registry entries are + * stored. Uses the {@link RegisteredServiceJsonSerializer} by default. + * + * @param configDirectory the config directory where service registry files can be found. + * @throws IOException the IO exception + */ + public JsonServiceRegistryDao(final File configDirectory) throws IOException { + this(Paths.get(configDirectory.getCanonicalPath())); + } + + @Override + public final RegisteredService save(final RegisteredService service) { + if (service.getId() == RegisteredService.INITIAL_IDENTIFIER_VALUE && service instanceof AbstractRegisteredService) { + LOGGER.debug("Service id not set. Calculating id based on system time..."); + ((AbstractRegisteredService) service).setId(System.nanoTime()); + } + final File f = makeFile(service); + try (final LockedOutputStream out = new LockedOutputStream(new FileOutputStream(f));) { + this.registeredServiceJsonSerializer.toJson(out, service); + + if (this.serviceMap.containsKey(service.getId())) { + LOGGER.debug("Found existing service definition by id [{}]. Saving...", service.getId()); + } + this.serviceMap.put(service.getId(), service); + LOGGER.debug("Saved service to [{}]", f.getCanonicalPath()); + } catch (final IOException e) { + throw new RuntimeException("IO error opening file stream.", e); + } + return findServiceById(service.getId()); + } + + @Override + public final synchronized boolean delete(final RegisteredService service) { + try { + final File f = makeFile(service); + final boolean result = f.delete(); + if (!result) { + LOGGER.warn("Failed to delete service definition file [{}]", f.getCanonicalPath()); + } else { + serviceMap.remove(service.getId()); + LOGGER.debug("Successfully deleted service definition file [{}]", f.getCanonicalPath()); + } + return result; + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public final synchronized List load() { + final Map temp = new ConcurrentHashMap<>(); + int errorCount = 0; + final Collection c = FileUtils.listFiles(this.serviceRegistryDirectory.toFile(), new String[] {FILE_EXTENSION}, true); + for (final File file : c) { + if (file.length() > 0) { + final RegisteredService service = loadRegisteredServiceFromFile(file); + if (service == null) { + errorCount++; + } else { + if (temp.containsKey(service.getId())) { + LOGGER.warn("Found a service definition [{}] with a duplicate id [{}]. " + + "This will overwrite previous service definitions and is likely a " + + "configuration problem. Make sure all services have a unique id and try again.", + service.getServiceId(), service.getId()); + } + temp.put(service.getId(), service); + } + } + } + if (errorCount == 0) { + this.serviceMap = temp; + } + return new ArrayList<>(this.serviceMap.values()); + } + + @Override + public final RegisteredService findServiceById(final long id) { + return serviceMap.get(id); + } + + /** + * Load registered service from file. + * + * @param file the file + * @return the registered service, or null if file cannot be read, is not found, is empty or parsing error occurs. + */ + RegisteredService loadRegisteredServiceFromFile(final File file) { + if (!file.canRead()) { + LOGGER.warn("[{}] is not readable. Check file permissions", file.getName()); + return null; + } + + if (!file.exists()) { + LOGGER.warn("[{}] is not found at the path specified", file.getName()); + return null; + } + + if (file.length() == 0) { + LOGGER.warn("[{}] appears to be empty so no service definition will be loaded", file.getName()); + return null; + } + + try (final BufferedInputStream in = new BufferedInputStream(new FileInputStream(file))) { + return this.registeredServiceJsonSerializer.fromJson(in); + } catch (final Exception e) { + LOGGER.error("Error reading configuration file", e); + } + return null; + } + + /** + * Insert registered service into the existing map. + * + * @param service the service + */ + void updateRegisteredService(final RegisteredService service) { + this.serviceMap.put(service.getId(), service); + } + + Path getServiceRegistryDirectory() { + return serviceRegistryDirectory; + } + + /** + * Creates a JSON file for a registered service. + * The file is named as [SERVICE-NAME]-[SERVICE-ID]-.{@value #FILE_EXTENSION} + * + * @param service Registered service. + * @return JSON file in service registry directory. + * @throws IllegalArgumentException if file name is invalid + */ + protected File makeFile(final RegisteredService service) { + final String fileName = StringUtils.remove(service.getName() + '-' + service.getId() + '.' + FILE_EXTENSION, " "); + try { + final File svcFile = new File(serviceRegistryDirectory.toFile(), fileName); + LOGGER.debug("Using [{}] as the service definition file", svcFile.getCanonicalPath()); + return svcFile; + } catch (final IOException e) { + LOGGER.warn("Service file name {} is invalid; Examine for illegal characters in the name.", fileName); + throw new IllegalArgumentException(e); + } + } + + /** + * Initialize watch service thread. + */ + private void initializeWatchServiceThread() { + final Thread thread = new Thread(new JsonServiceRegistryConfigWatcher(this)); + thread.start(); + } + + /** + * Refreshes the services manager, forcing it to reload. + */ + void refreshServicesManager() { + final ReloadableServicesManager manager = this.applicationContext.getBean(ReloadableServicesManager.class); + manager.reload(); + } + + @Override + public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/PrincipalAttributeRegisteredServiceUsernameProvider.java b/cas-server-core/src/main/java/org/jasig/cas/services/PrincipalAttributeRegisteredServiceUsernameProvider.java new file mode 100644 index 000000000000..7488fd631dd2 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/PrincipalAttributeRegisteredServiceUsernameProvider.java @@ -0,0 +1,120 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.validation.constraints.NotNull; + +/** + * Determines the username for this registered service based on a principal attribute. + * If the attribute is not found, default principal id is returned. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class PrincipalAttributeRegisteredServiceUsernameProvider implements RegisteredServiceUsernameAttributeProvider { + + private static final long serialVersionUID = -3546719400741715137L; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @NotNull + private final String usernameAttribute; + + /** + * Private constructor to get around serialization issues. + */ + private PrincipalAttributeRegisteredServiceUsernameProvider() { + this.usernameAttribute = null; + } + + /** + * Instantiates a new default registered service username provider. + * + * @param usernameAttribute the username attribute + */ + public PrincipalAttributeRegisteredServiceUsernameProvider(@NotNull final String usernameAttribute) { + this.usernameAttribute = usernameAttribute; + } + + public String getUsernameAttribute() { + return this.usernameAttribute; + } + + @Override + public String resolveUsername(final Principal principal, final Service service) { + String principalId = principal.getId(); + + if (principal.getAttributes().containsKey(this.usernameAttribute)) { + principalId = principal.getAttributes().get(this.usernameAttribute).toString(); + } else { + logger.warn("Principal [{}] did not have attribute [{}] among attributes [{}] so CAS cannot " + + "provide the user attribute the service expects. " + + "CAS will instead return the default principal id [{}]", + principalId, + this.usernameAttribute, + principal.getAttributes(), + principalId); + } + + logger.debug("Principal id to return is [{}]. The default principal id is [{}].", + principalId, principal.getId()); + return principalId; + } + + @Override + public String toString() { + final ToStringBuilder toStringBuilder = new ToStringBuilder(null, ToStringStyle.SHORT_PREFIX_STYLE); + toStringBuilder.append("usernameAttribute", this.usernameAttribute); + return toStringBuilder.toString(); + } + + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj.getClass() != getClass()) { + return false; + } + final PrincipalAttributeRegisteredServiceUsernameProvider rhs = + (PrincipalAttributeRegisteredServiceUsernameProvider) obj; + return new EqualsBuilder() + .append(this.usernameAttribute, rhs.usernameAttribute) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder() + .append(usernameAttribute) + .toHashCode(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/RefuseRegisteredServiceProxyPolicy.java b/cas-server-core/src/main/java/org/jasig/cas/services/RefuseRegisteredServiceProxyPolicy.java new file mode 100644 index 000000000000..defc70a1c001 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/RefuseRegisteredServiceProxyPolicy.java @@ -0,0 +1,66 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import java.net.URL; + +/** + * A proxy policy that disallows proxying. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class RefuseRegisteredServiceProxyPolicy implements RegisteredServiceProxyPolicy { + + private static final long serialVersionUID = -5718445151129901484L; + + @Override + public boolean isAllowedToProxy() { + return false; + } + + @Override + public boolean isAllowedProxyCallbackUrl(final URL pgtUrl) { + return false; + } + + @Override + public boolean equals(final Object o) { + if (o == null) { + return false; + } + + if (this == o) { + return true; + } + + if (!(o instanceof RegisteredServiceProxyPolicy)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + final HashCodeBuilder bldr = new HashCodeBuilder(13, 133); + return bldr.appendSuper(super.hashCode()).toHashCode(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/RegexMatchingRegisteredServiceProxyPolicy.java b/cas-server-core/src/main/java/org/jasig/cas/services/RegexMatchingRegisteredServiceProxyPolicy.java new file mode 100644 index 000000000000..6737d9a8b593 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/RegexMatchingRegisteredServiceProxyPolicy.java @@ -0,0 +1,103 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import javax.validation.constraints.NotNull; +import java.net.URL; +import java.util.regex.Pattern; + +/** + * A proxy policy that only allows proxying to pgt urls + * that match the specified regex pattern. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class RegexMatchingRegisteredServiceProxyPolicy implements RegisteredServiceProxyPolicy { + + private static final long serialVersionUID = -211069319543047324L; + + private final Pattern pattern; + + /** + * Instantiates a new Regex matching registered service proxy policy. + * Required for serialization. + */ + protected RegexMatchingRegisteredServiceProxyPolicy() { + this.pattern = null; + } + + /** + * Init the policy with the pgt url regex pattern that + * will determine the urls allowed to receive the pgt. + * The matching by default is done in a case insensitive manner. + * @param pgtUrlPattern the pgt url pattern + */ + public RegexMatchingRegisteredServiceProxyPolicy(@NotNull final String pgtUrlPattern) { + this.pattern = Pattern.compile(pgtUrlPattern, Pattern.CASE_INSENSITIVE); + } + + /** + * Gets the pattern. + * + * @return the pattern + */ + protected Pattern getPattern() { + return this.pattern; + } + + @Override + public boolean isAllowedToProxy() { + return true; + } + + @Override + public int hashCode() { + return new HashCodeBuilder(13, 117).append(this.pattern.pattern()).toHashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj.getClass() != getClass()) { + return false; + } + final RegexMatchingRegisteredServiceProxyPolicy rhs = (RegexMatchingRegisteredServiceProxyPolicy) obj; + return new EqualsBuilder().append(this.pattern.pattern(), rhs.pattern.pattern()).isEquals(); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append(this.pattern.pattern()).toString(); + } + + @Override + public boolean isAllowedProxyCallbackUrl(final URL pgtUrl) { + return this.pattern.matcher(pgtUrl.toExternalForm()).find(); + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/RegexRegisteredService.java b/cas-server-core/src/main/java/org/jasig/cas/services/RegexRegisteredService.java new file mode 100644 index 000000000000..6bc6adabd189 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/RegexRegisteredService.java @@ -0,0 +1,76 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.jasig.cas.authentication.principal.Service; + +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; + +import java.util.regex.Pattern; + +/** + * Mutable registered service that uses Java regular expressions for service matching. + * Matching is case insensitive, and is successful, if, and only if, the entire region + * sequence matches the pattern. + * + * @author Marvin S. Addison + * @author Misagh Moayyed + * @since 3.4 + */ +@Entity +@DiscriminatorValue("regex") +public class RegexRegisteredService extends AbstractRegisteredService { + + private static final long serialVersionUID = -8258660210826975771L; + + private transient Pattern servicePattern; + + public void setServiceId(final String id) { + serviceId = id; + } + + @Override + public boolean matches(final Service service) { + if (servicePattern == null) { + servicePattern = createPattern(serviceId); + } + return service != null && servicePattern.matcher(service.getId()).matches(); + } + + @Override + protected AbstractRegisteredService newInstance() { + return new RegexRegisteredService(); + } + + /** + * Creates the pattern. Matching is by default + * case insensitive. + * + * @param pattern the pattern, may not be null. + * @return the pattern + */ + private Pattern createPattern(final String pattern) { + if (pattern == null) { + throw new IllegalArgumentException("Pattern cannot be null."); + } + + return Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/RegisteredServiceImpl.java b/cas-server-core/src/main/java/org/jasig/cas/services/RegisteredServiceImpl.java new file mode 100644 index 000000000000..fda33c89a3cc --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/RegisteredServiceImpl.java @@ -0,0 +1,73 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.jasig.cas.authentication.principal.Service; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; + +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; + +/** + * @deprecated As for 4.1. Consider using {@link org.jasig.cas.services.RegexRegisteredService} instead. + * Mutable registered service that uses Ant path patterns for service matching. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.1 + */ +@Entity +@DiscriminatorValue("ant") +@Deprecated +public class RegisteredServiceImpl extends AbstractRegisteredService { + + /** Unique Id for serialization. */ + private static final long serialVersionUID = -5906102762271197627L; + + private static final PathMatcher PATH_MATCHER = new AntPathMatcher(); + + /** + * @deprecated As of 4.1. Consider using regex patterns instead + * via {@link org.jasig.cas.services.RegexRegisteredService}. + * Instantiates a new registered service. + */ + @Deprecated + public RegisteredServiceImpl() { + super(); + logger.warn("[{}] is deprecated and will be removed in future CAS releases. Consider using [{}] instead.", + this.getClass().getSimpleName(), RegexRegisteredService.class.getSimpleName()); + } + + @Override + public void setServiceId(final String id) { + this.serviceId = id; + } + + @Override + public boolean matches(final Service service) { + return service != null && PATH_MATCHER.match(serviceId.toLowerCase(), service.getId().toLowerCase()); + } + + @Override + protected AbstractRegisteredService newInstance() { + return new RegisteredServiceImpl(); + } +} + diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/RegisteredServicePublicKeyImpl.java b/cas-server-core/src/main/java/org/jasig/cas/services/RegisteredServicePublicKeyImpl.java new file mode 100644 index 000000000000..a2ac5a4a630a --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/RegisteredServicePublicKeyImpl.java @@ -0,0 +1,146 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.services; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.jasig.cas.util.PublicKeyFactoryBean; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.FileSystemResource; + +import java.io.Serializable; +import java.security.PublicKey; + +/** + * Represents a public key for a CAS registered service. + * @author Misagh Moayyed + * @since 4.1 + */ +public final class RegisteredServicePublicKeyImpl implements Serializable, RegisteredServicePublicKey { + private static final long serialVersionUID = -8497658523695695863L; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private String location; + + private String algorithm; + + private Class publicKeyFactoryBeanClass = PublicKeyFactoryBean.class; + + /** + * Instantiates a new Registered service public key impl. + * Required for proper serialization. + */ + private RegisteredServicePublicKeyImpl() {} + + /** + * Instantiates a new Registered service public key impl. + * + * @param location the location + * @param algorithm the algorithm + */ + public RegisteredServicePublicKeyImpl(final String location, final String algorithm) { + this.location = location; + this.algorithm = algorithm; + } + + public void setLocation(final String location) { + this.location = location; + } + + public void setAlgorithm(final String algorithm) { + this.algorithm = algorithm; + } + + @Override + public String getLocation() { + return this.location; + } + + @Override + public String getAlgorithm() { + return this.algorithm; + } + + /** + * Sets public key factory bean class. + * + * @param publicKeyFactoryBeanClass the public key factory bean class + */ + public void setPublicKeyFactoryBeanClass(final Class publicKeyFactoryBeanClass) { + this.publicKeyFactoryBeanClass = publicKeyFactoryBeanClass; + } + + @Override + public PublicKey createInstance() throws Exception { + try { + final PublicKeyFactoryBean factory = publicKeyFactoryBeanClass.newInstance(); + if (this.location.startsWith("classpath:")) { + factory.setLocation(new ClassPathResource(StringUtils.removeStart(this.location, "classpath:"))); + } else { + factory.setLocation(new FileSystemResource(this.location)); + } + factory.setAlgorithm(this.algorithm); + factory.setSingleton(false); + return factory.getObject(); + } catch (final Exception e) { + logger.warn(e.getMessage(), e); + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("location", this.location) + .append("algorithm", this.algorithm) + .toString(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj.getClass() != getClass()) { + return false; + } + final RegisteredServicePublicKeyImpl rhs = (RegisteredServicePublicKeyImpl) obj; + return new EqualsBuilder() + .append(this.location, rhs.location) + .append(this.algorithm, rhs.algorithm) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder() + .append(location) + .append(algorithm) + .toHashCode(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/ReturnAllAttributeReleasePolicy.java b/cas-server-core/src/main/java/org/jasig/cas/services/ReturnAllAttributeReleasePolicy.java new file mode 100644 index 000000000000..0cae8f56d844 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/ReturnAllAttributeReleasePolicy.java @@ -0,0 +1,36 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import java.util.Map; + +/** + * Return all attributes for the service, regardless of service settings. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class ReturnAllAttributeReleasePolicy extends AbstractAttributeReleasePolicy { + + private static final long serialVersionUID = 5519257723778012771L; + + @Override + protected Map getAttributesInternal(final Map resolvedAttributes) { + return resolvedAttributes; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/ReturnAllowedAttributeReleasePolicy.java b/cas-server-core/src/main/java/org/jasig/cas/services/ReturnAllowedAttributeReleasePolicy.java new file mode 100644 index 000000000000..e452659f5f2a --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/ReturnAllowedAttributeReleasePolicy.java @@ -0,0 +1,70 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Return only the collection of allowed attributes out of what's resolved + * for the principal. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class ReturnAllowedAttributeReleasePolicy extends AbstractAttributeReleasePolicy { + + private static final long serialVersionUID = -5771481877391140569L; + + private List allowedAttributes = Collections.emptyList(); + + /** + * Sets the allowed attributes. + * + * @param allowed the allowed attributes. + */ + public void setAllowedAttributes(final List allowed) { + this.allowedAttributes = allowed; + } + + /** + * Gets the allowed attributes. + * + * @return the allowed attributes + */ + protected List getAllowedAttributes() { + return Collections.unmodifiableList(this.allowedAttributes); + } + + @Override + protected Map getAttributesInternal(final Map resolvedAttributes) { + final Map attributesToRelease = new HashMap<>(resolvedAttributes.size()); + + for (final String attribute : this.allowedAttributes) { + final Object value = resolvedAttributes.get(attribute); + + if (value != null) { + logger.debug("Found attribute [{}] in the list of allowed attributes", attribute); + attributesToRelease.put(attribute, value); + } + } + return attributesToRelease; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/ReturnMappedAttributeReleasePolicy.java b/cas-server-core/src/main/java/org/jasig/cas/services/ReturnMappedAttributeReleasePolicy.java new file mode 100644 index 000000000000..0db77e05847d --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/ReturnMappedAttributeReleasePolicy.java @@ -0,0 +1,72 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; + +/** + * Return a collection of allowed attributes for the principal, but additionally, + * offers the ability to rename attributes on a per-service level. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class ReturnMappedAttributeReleasePolicy extends AbstractAttributeReleasePolicy { + + private static final long serialVersionUID = -6249488544306639050L; + + private Map allowedAttributes = new TreeMap<>(); + + /** + * Sets the allowed attributes. + * + * @param allowed the allowed attributes. + */ + public void setAllowedAttributes(final Map allowed) { + this.allowedAttributes = allowed; + } + + /** + * Gets the allowed attributes. + * + * @return the allowed attributes + */ + protected Map getAllowedAttributes() { + return new TreeMap(this.allowedAttributes); + } + + @Override + protected Map getAttributesInternal(final Map resolvedAttributes) { + final Map attributesToRelease = new HashMap<>(resolvedAttributes.size()); + + for (final Map.Entry entry : this.allowedAttributes.entrySet()) { + final String key = entry.getKey(); + final Object value = resolvedAttributes.get(key); + + if (value != null) { + final String mappedAttributeName = entry.getValue(); + logger.debug("Found attribute [{}] in the list of allowed attributes, mapped to the name [{}]", + key, mappedAttributeName); + attributesToRelease.put(mappedAttributeName, value); + } + } + return attributesToRelease; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/ServiceContext.java b/cas-server-core/src/main/java/org/jasig/cas/services/ServiceContext.java new file mode 100644 index 000000000000..322e6606e337 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/ServiceContext.java @@ -0,0 +1,72 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import javax.validation.constraints.NotNull; + +import org.jasig.cas.authentication.principal.Service; + +/** + * Simple container for holding a service principal and its corresponding registered serivce. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class ServiceContext { + + /** Service principal. */ + @NotNull + private final Service service; + + /** Registered service corresponding to service principal. */ + @NotNull + private final RegisteredService registeredService; + + /** + * Creates a new instance with required parameters. + * + * @param service Service principal. + * @param registeredService Registered service corresponding to given service. + */ + public ServiceContext(@NotNull final Service service, @NotNull final RegisteredService registeredService) { + this.service = service; + this.registeredService = registeredService; + if (!registeredService.matches(service)) { + throw new IllegalArgumentException("Registered service does not match given service."); + } + } + + /** + * Gets the service principal. + * + * @return Non-null service principal. + */ + public Service getService() { + return service; + } + + /** + * Gets the registered service for the service principal. + * + * @return Non-null registered service. + */ + public RegisteredService getRegisteredService() { + return registeredService; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/UnauthorizedProxyingException.java b/cas-server-core/src/main/java/org/jasig/cas/services/UnauthorizedProxyingException.java new file mode 100644 index 000000000000..9ed12098116f --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/UnauthorizedProxyingException.java @@ -0,0 +1,61 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +/** + * Exception thrown when a service attempts to proxy when it is not allowed to. + * + * @author Scott Battaglia + * @since 3.1 + */ +public class UnauthorizedProxyingException extends UnauthorizedServiceException { + /** The code description. */ + public static final String CODE = "UNAUTHORIZED_SERVICE_PROXY"; + + /** + * Comment for serialVersionUID. + */ + private static final long serialVersionUID = -7307803750894078575L; + + /** + * Instantiates a new unauthorized proxying exception. + */ + public UnauthorizedProxyingException() { + super(CODE); + } + + /** + * Instantiates a new unauthorized proxying exception. + * + * @param message the message + * @param cause the cause + */ + public UnauthorizedProxyingException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Instantiates a new unauthorized proxying exception. + * + * @param message the message + */ + public UnauthorizedProxyingException(final String message) { + super(message); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/UnauthorizedServiceException.java b/cas-server-core/src/main/java/org/jasig/cas/services/UnauthorizedServiceException.java new file mode 100644 index 000000000000..5ac258fc339a --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/UnauthorizedServiceException.java @@ -0,0 +1,78 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +/** + * Exception that is thrown when an Unauthorized Service attempts to use CAS. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public class UnauthorizedServiceException extends RuntimeException { + + /** Error code that indicates the service is unauthorized for use. **/ + public static final String CODE_UNAUTHZ_SERVICE = "screen.service.error.message"; + + /** Exception object that indicates the service manager is empty with no service definitions. **/ + public static final String CODE_EMPTY_SVC_MGMR = "screen.service.empty.error.message"; + + /** The Unique ID for serialization. */ + private static final long serialVersionUID = 3905807495715960369L; + + private final String code; + + /** + * Construct the exception object with the associated error code. + * @param message the error message + */ + public UnauthorizedServiceException(final String message) { + this(null, message); + } + + /** + * Constructs an UnauthorizedServiceException with a custom message and the + * root cause of this exception. + * + * @param message an explanatory message. Maybe null or blank. + * @param code the error code mapped to the messaged bundle. + */ + public UnauthorizedServiceException(final String code, final String message) { + super(message); + this.code = code; + } + /** + * Constructs an UnauthorizedServiceException with a custom message and the + * root cause of this exception. + * + * @param message an explanatory message. + * @param cause the root cause of the exception. + */ + public UnauthorizedServiceException(final String message, final Throwable cause) { + super(message, cause); + this.code = null; + } + + /** + * The error code associated with this exception. + * @return the error code. + */ + public final String getCode() { + return this.code; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/UnauthorizedServiceForPrincipalException.java b/cas-server-core/src/main/java/org/jasig/cas/services/UnauthorizedServiceForPrincipalException.java new file mode 100644 index 000000000000..58d4f09d691b --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/UnauthorizedServiceForPrincipalException.java @@ -0,0 +1,64 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.services; + +/** + * This is {@link UnauthorizedServiceForPrincipalException} + * thrown when an attribute is missing from principal + * attribute release policy that would otherwise grant access + * to the service that is requesting authentication. + * + * @author Misagh Moayyed mmoayyed@unicon.net + * @since 4.1 + */ +public final class UnauthorizedServiceForPrincipalException extends UnauthorizedServiceException { + + private static final long serialVersionUID = 8909291297815558561L; + + /** The code description. */ + private static final String CODE = "service.not.authorized.missing.attr"; + + /** + * Instantiates a new unauthorized sso service exception. + */ + public UnauthorizedServiceForPrincipalException() { + super(CODE, ""); + } + + /** + * Instantiates a new unauthorized sso service exception. + * + * @param message the message + * @param cause the cause + */ + public UnauthorizedServiceForPrincipalException(final String message, + final Throwable cause) { + super(message, cause); + } + + /** + * Instantiates a new unauthorized sso service exception. + * + * @param message the message + */ + public UnauthorizedServiceForPrincipalException(final String message) { + super(message); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/UnauthorizedSsoServiceException.java b/cas-server-core/src/main/java/org/jasig/cas/services/UnauthorizedSsoServiceException.java new file mode 100644 index 000000000000..3193769a6f54 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/UnauthorizedSsoServiceException.java @@ -0,0 +1,67 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +/** + * Exception thrown when a service attempts to use SSO when it should not be + * allowed to. + * + * @author Scott Battaglia + + * @since 3.1 + */ +public class UnauthorizedSsoServiceException extends + UnauthorizedServiceException { + + /** + * Comment for serialVersionUID. + */ + private static final long serialVersionUID = 8909291297815558561L; + + /** The code description. */ + private static final String CODE = "service.not.authorized.sso"; + + /** + * Instantiates a new unauthorized sso service exception. + */ + public UnauthorizedSsoServiceException() { + this(CODE); + } + + /** + * Instantiates a new unauthorized sso service exception. + * + * @param message the message + * @param cause the cause + */ + public UnauthorizedSsoServiceException(final String message, + final Throwable cause) { + super(message, cause); + } + + /** + * Instantiates a new unauthorized sso service exception. + * + * @param message the message + */ + public UnauthorizedSsoServiceException(final String message) { + super(message); + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/jmx/AbstractServicesManagerMBean.java b/cas-server-core/src/main/java/org/jasig/cas/services/jmx/AbstractServicesManagerMBean.java new file mode 100644 index 000000000000..4adcd4ab3d11 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/jmx/AbstractServicesManagerMBean.java @@ -0,0 +1,127 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services.jmx; + +import org.jasig.cas.services.DefaultRegisteredServiceAccessStrategy; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.springframework.jmx.export.annotation.ManagedAttribute; +import org.springframework.jmx.export.annotation.ManagedOperation; +import org.springframework.jmx.export.annotation.ManagedOperationParameter; +import org.springframework.util.Assert; + +import javax.validation.constraints.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * Abstract base class to support both the {@link org.jasig.cas.services.ServicesManager} and the + * {@link org.jasig.cas.services.ReloadableServicesManager}. + * + * @author Tobias Trelle + * @author Scott Battaglia + * @since 3.4.4 + */ +public abstract class AbstractServicesManagerMBean { + + @NotNull + private final T servicesManager; + + /** + * Instantiates a new abstract services manager m bean. + * + * @param svcMgr the svc mgr + */ + protected AbstractServicesManagerMBean(final T svcMgr) { + this.servicesManager = svcMgr; + } + + protected final T getServicesManager() { + return this.servicesManager; + } + + /** + * Gets the registered services as strings. + * + * @return the registered services as strings + */ + @ManagedAttribute(description = "Retrieves the list of Registered Services in a slightly friendlier output.") + public final List getRegisteredServicesAsStrings() { + final List services = new ArrayList<>(); + + for (final RegisteredService r : this.servicesManager.getAllServices()) { + services.add(new StringBuilder().append("id: ").append(r.getId()) + .append(" name: ").append(r.getName()) + .append(" serviceId: ").append(r.getServiceId()) + .toString()); + } + + return services; + } + + /** + * Removes the service. + * + * @param id the id + * @return the registered service + */ + @ManagedOperation(description = "Can remove a service based on its identifier.") + @ManagedOperationParameter(name="id", description = "the identifier to remove") + public final RegisteredService removeService(final long id) { + return this.servicesManager.delete(id); + } + + /** + * Disable service. + * + * @param id the id + */ + @ManagedOperation(description = "Disable a service by id.") + @ManagedOperationParameter(name="id", description = "the identifier to disable") + public final void disableService(final long id) { + changeEnabledState(id, false); + } + + /** + * Enable service. + * + * @param id the id + */ + @ManagedOperation(description = "Enable a service by its id.") + @ManagedOperationParameter(name="id", description = "the identifier to enable.") + public final void enableService(final long id) { + changeEnabledState(id, true); + } + + /** + * Change enabled state. + * + * @param id the id + * @param newState the new state + */ + private void changeEnabledState(final long id, final boolean newState) { + final RegisteredService r = this.servicesManager.findServiceBy(id); + Assert.notNull(r, "invalid RegisteredService id"); + + // we screwed up our APIs in older versions of CAS, so we need to CAST this to do anything useful. + ((DefaultRegisteredServiceAccessStrategy) r.getAccessStrategy()).setEnabled(newState); + this.servicesManager.save(r); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/jmx/ReloadableServicesManagerMBean.java b/cas-server-core/src/main/java/org/jasig/cas/services/jmx/ReloadableServicesManagerMBean.java new file mode 100644 index 000000000000..d723a6d5c7ad --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/jmx/ReloadableServicesManagerMBean.java @@ -0,0 +1,56 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services.jmx; + +import org.jasig.cas.services.ReloadableServicesManager; +import org.springframework.jmx.export.annotation.ManagedOperation; +import org.springframework.jmx.export.annotation.ManagedResource; + +/** + * Provides capabilities to reload a {@link org.jasig.cas.services.ReloadableServicesManager} from JMX. + *

+ * You should only expose either this class or + * the {@link org.jasig.cas.services.jmx.ServicesManagerMBean}, but not both. + * + * @author Scott Battaglia + + * @since 3.4.4 + */ +@ManagedResource(objectName = "CAS:name=JasigCasServicesManagerMBean", + description = "Exposes the services management tool via JMX", log = true, logFile="jasig_cas_jmx.logger", + currencyTimeLimit = 15) +public final class ReloadableServicesManagerMBean extends AbstractServicesManagerMBean { + + /** + * Instantiates a new reloadable services manager m bean. + * + * @param reloadableServicesManager the reloadable services manager + */ + public ReloadableServicesManagerMBean(final ReloadableServicesManager reloadableServicesManager) { + super(reloadableServicesManager); + } + + /** + * Reload services that are provided by the manager. + */ + @ManagedOperation(description = "Reloads the list of the services from the persistence storage.") + public void reload() { + getServicesManager().reload(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/jmx/ServicesManagerMBean.java b/cas-server-core/src/main/java/org/jasig/cas/services/jmx/ServicesManagerMBean.java new file mode 100644 index 000000000000..a2f1d4bb3080 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/jmx/ServicesManagerMBean.java @@ -0,0 +1,44 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services.jmx; + +import org.jasig.cas.services.ServicesManager; +import org.springframework.jmx.export.annotation.ManagedResource; + +/** + * Supports the basic {@link org.jasig.cas.services.ServicesManager}. + * + * @author Scott Battaglia + * @since 3.4.4 + */ + +@ManagedResource(objectName = "CAS:name=JasigCasServicesManagerMBean", + description = "Exposes the services management tool via JMX", log = true, logFile="jasig_cas_jmx.logger", + currencyTimeLimit = 15) +public final class ServicesManagerMBean extends AbstractServicesManagerMBean { + + /** + * Instantiates a new services manager m bean. + * + * @param servicesManager the services manager + */ + public ServicesManagerMBean(final ServicesManager servicesManager) { + super(servicesManager); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/package.html b/cas-server-core/src/main/java/org/jasig/cas/services/package.html new file mode 100644 index 000000000000..c31c084fdad6 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/package.html @@ -0,0 +1,33 @@ + + + +

This package is contains classes related to the restriction of CAS +usage to a particular set of services. This is accomplished via a +combination of registries and interceptors.

+

The ServiceRegistry, with its default implementation of +DefaultServiceRegistry contains the list of RegisteredServices allowed +to access CAS. This list is periodically refreshed via the +ServiceRegistryReloader.

+

CAS itself is protected by a group of interceptors found in the +subpackage advice.

+ + diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/support/RegisteredServiceRegexAttributeFilter.java b/cas-server-core/src/main/java/org/jasig/cas/services/support/RegisteredServiceRegexAttributeFilter.java new file mode 100644 index 000000000000..1db624e9d097 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/support/RegisteredServiceRegexAttributeFilter.java @@ -0,0 +1,223 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services.support; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.jasig.cas.services.AttributeFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + + +/** + * The regex filter that is responsible to make sure only attributes that match a certain regex pattern + * registered service are released. + * + * @author Misagh Moayyed + * @since 4.0.0 + */ +public final class RegisteredServiceRegexAttributeFilter implements AttributeFilter { + private static final long serialVersionUID = 403015306984610128L; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @NotNull + private Pattern pattern; + + /** + * Instantiates a new Registered service regex attribute filter. + * Required for serialization. + */ + protected RegisteredServiceRegexAttributeFilter() {} + + /** + * Instantiates a new registered service regex attribute filter. + * + * @param regex the regex + */ + public RegisteredServiceRegexAttributeFilter(final String regex) { + this.pattern = Pattern.compile(regex); + } + + /** + * Gets the pattern. + * + * @return the pattern + */ + protected Pattern getPattern() { + return this.pattern; + } + + /** + * {@inheritDoc} + * + * Given attribute values may be an extension of {@link Collection}, {@link Map} or an array. + *
    + *
  • The filtering operation is non-recursive.
  • + *
  • Multi-valued attributes such as those of type {@link Collection} and + * {@link Map} are expected to allow casting to Map<String, String> + * or Collection<String>. + * Values that are of type array are expected to allow casting to String[]. + *
  • + *
  • Multi-valued attributes are always put back into the final released collection of + * attributes as String[].
  • + *
  • If the final filtered collection is empty, it will not be put into the collection of attributes.
  • + *
+ */ + @Override + @SuppressWarnings("unchecked") + public Map filter(final Map givenAttributes) { + final Map attributesToRelease = new HashMap<>(); + for (final Map.Entry entry: givenAttributes.entrySet()) { + final String attributeName = entry.getKey(); + final Object attributeValue = entry.getValue(); + + logger.debug("Received attribute [{}] with value [{}]", attributeName, attributeValue); + if (attributeValue != null) { + if (attributeValue instanceof Collection) { + logger.trace("Attribute value {} is a collection", attributeValue); + final String[] filteredAttributes = filterArrayAttributes( + ((Collection) attributeValue).toArray(new String[] {}), attributeName); + if (filteredAttributes.length > 0) { + attributesToRelease.put(attributeName, Arrays.asList(filteredAttributes)); + } + } else if (attributeValue.getClass().isArray()) { + logger.trace("Attribute value {} is an array", attributeValue); + final String[] filteredAttributes = filterArrayAttributes((String[]) attributeValue, attributeName); + if (filteredAttributes.length > 0) { + attributesToRelease.put(attributeName, Arrays.asList(filteredAttributes)); + } + } else if (attributeValue instanceof Map) { + logger.trace("Attribute value {} is a map", attributeValue); + final Map filteredAttributes = filterMapAttributes((Map) attributeValue); + if (filteredAttributes.size() > 0) { + attributesToRelease.put(attributeName, filteredAttributes); + } + } else { + logger.trace("Attribute value {} is a string", attributeValue); + final String attrValue = attributeValue.toString(); + if (patternMatchesAttributeValue(attrValue)) { + logReleasedAttributeEntry(attributeName, attrValue); + attributesToRelease.put(attributeName, attrValue); + } + } + } + } + + logger.debug("Received {} attributes. Filtered and released {}", givenAttributes.size(), + attributesToRelease.size()); + return attributesToRelease; + } + + /** + * Filter map attributes based on the values given. + * + * @param valuesToFilter the values to filter + * @return the map + */ + private Map filterMapAttributes(final Map valuesToFilter) { + final Map attributesToFilter = new HashMap<>(valuesToFilter.size()); + for (final Map.Entry entry: valuesToFilter.entrySet()) { + final String attributeName = entry.getKey(); + final String attributeValue = entry.getValue(); + if (patternMatchesAttributeValue(attributeValue)) { + logReleasedAttributeEntry(attributeName, attributeValue); + attributesToFilter.put(attributeName, valuesToFilter.get(attributeName)); + } + } + return attributesToFilter; + } + + /** + * Determine whether pattern matches attribute value. + * + * @param value the value + * @return true, if successful + */ + private boolean patternMatchesAttributeValue(final String value) { + return this.pattern.matcher(value).matches(); + } + + /** + * Filter array attributes. + * + * @param valuesToFilter the values to filter + * @param attributeName the attribute name + * @return the string[] + */ + private String[] filterArrayAttributes(final String[] valuesToFilter, final String attributeName) { + final List vector = new ArrayList<>(valuesToFilter.length); + for (final String attributeValue : valuesToFilter) { + if (patternMatchesAttributeValue(attributeValue)) { + logReleasedAttributeEntry(attributeName, attributeValue); + vector.add(attributeValue); + } + } + return vector.toArray(new String[] {}); + } + + /** + * Logs the released attribute entry. + * + * @param attributeName the attribute name + * @param attributeValue the attribute value + */ + private void logReleasedAttributeEntry(final String attributeName, final String attributeValue) { + logger.debug("The attribute value [{}] for attribute name {} matches the pattern {}. Releasing attribute...", + attributeValue, attributeName, this.pattern.pattern()); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 83).append(this.pattern).toHashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj.getClass() != getClass()) { + return false; + } + final RegisteredServiceRegexAttributeFilter rhs = (RegisteredServiceRegexAttributeFilter) obj; + return new EqualsBuilder().append(this.pattern.pattern(), rhs.getPattern().pattern()).isEquals(); + } + + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("pattern", this.pattern.pattern()) + .toString(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/AbstractTicket.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/AbstractTicket.java new file mode 100644 index 000000000000..ecc58937cf18 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/AbstractTicket.java @@ -0,0 +1,168 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket; + +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.springframework.util.Assert; + +import javax.persistence.Column; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; + +/** + * Abstract implementation of a ticket that handles all ticket state for + * policies. Also incorporates properties common among all tickets. As this is + * an abstract class, it cannnot be instanciated. It is recommended that + * implementations of the Ticket interface extend the AbstractTicket as it + * handles common functionality amongst different ticket types (such as state + * updating). + * + * AbstractTicket does not provide a protected Logger instance to + * avoid instantiating many such Loggers at runtime (there will be many instances + * of subclasses of AbstractTicket in a typical running CAS server). Instead + * subclasses should use static Logger instances. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +@MappedSuperclass +public abstract class AbstractTicket implements Ticket, TicketState { + + private static final long serialVersionUID = -8506442397878267555L; + + /** The ExpirationPolicy this ticket will be following. */ + @Lob + @Column(name="EXPIRATION_POLICY", length = 1000000, nullable=false) + private ExpirationPolicy expirationPolicy; + + /** The unique identifier for this ticket. */ + @Id + @Column(name="ID", nullable=false) + private String id; + + /** The TicketGrantingTicket this is associated with. */ + @ManyToOne(targetEntity=TicketGrantingTicketImpl.class) + private TicketGrantingTicket ticketGrantingTicket; + + /** The last time this ticket was used. */ + @Column(name="LAST_TIME_USED") + private long lastTimeUsed; + + /** The previous last time this ticket was used. */ + @Column(name="PREVIOUS_LAST_TIME_USED") + private long previousLastTimeUsed; + + /** The time the ticket was created. */ + @Column(name="CREATION_TIME") + private long creationTime; + + /** The number of times this was used. */ + @Column(name="NUMBER_OF_TIMES_USED") + private int countOfUses; + + /** + * Instantiates a new abstract ticket. + */ + protected AbstractTicket() { + // nothing to do + } + + /** + * Constructs a new Ticket with a unique id, a possible parent Ticket (can + * be null) and a specified Expiration Policy. + * + * @param id the unique identifier for the ticket + * @param ticket the parent TicketGrantingTicket + * @param expirationPolicy the expiration policy for the ticket. + * @throws IllegalArgumentException if the id or expiration policy is null. + */ + public AbstractTicket(final String id, final TicketGrantingTicket ticket, + final ExpirationPolicy expirationPolicy) { + Assert.notNull(expirationPolicy, "expirationPolicy cannot be null"); + Assert.notNull(id, "id cannot be null"); + + this.id = id; + this.creationTime = System.currentTimeMillis(); + this.lastTimeUsed = System.currentTimeMillis(); + this.expirationPolicy = expirationPolicy; + this.ticketGrantingTicket = ticket; + } + + public final String getId() { + return this.id; + } + + /** + * Records the previous last time this ticket was used as well as + * the last usage time. The ticket usage count is also incremented. + * + *

Tickets themselves are solely responsible to maintain their state. The + * determination of ticket usage is left up to the implementation and + * the specific ticket type. + * + * @see ExpirationPolicy + */ + protected final void updateState() { + this.previousLastTimeUsed = this.lastTimeUsed; + this.lastTimeUsed = System.currentTimeMillis(); + this.countOfUses++; + } + + public final int getCountOfUses() { + return this.countOfUses; + } + + public final long getCreationTime() { + return this.creationTime; + } + + public final TicketGrantingTicket getGrantingTicket() { + return this.ticketGrantingTicket; + } + + public final long getLastTimeUsed() { + return this.lastTimeUsed; + } + + public final long getPreviousTimeUsed() { + return this.previousLastTimeUsed; + } + + public final boolean isExpired() { + return this.expirationPolicy.isExpired(this) + || (getGrantingTicket() != null && getGrantingTicket().isExpired()) + || isExpiredInternal(); + } + + protected boolean isExpiredInternal() { + return false; + } + + @Override + public final int hashCode() { + return new HashCodeBuilder().append(this.getId()).toHashCode(); + } + + @Override + public final String toString() { + return this.getId(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/ServiceTicketImpl.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/ServiceTicketImpl.java new file mode 100644 index 000000000000..50775e743f8f --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/ServiceTicketImpl.java @@ -0,0 +1,157 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; +import org.springframework.util.Assert; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Lob; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; + +/** + * Domain object representing a Service Ticket. A service ticket grants specific + * access to a particular service. It will only work for a particular service. + * Generally, it is a one time use Ticket, but the specific expiration policy + * can be anything. + * + * @author Scott Battaglia + + * @since 3.0.0 + */ +@Entity +@Table(name="SERVICETICKET") +public final class ServiceTicketImpl extends AbstractTicket implements + ServiceTicket { + + /** Unique Id for serialization. */ + private static final long serialVersionUID = -4223319704861765405L; + + /** The service this ticket is valid for. */ + @Lob + @Column(name="SERVICE", nullable=false) + private Service service; + + /** Is this service ticket the result of a new login. */ + @Column(name="FROM_NEW_LOGIN", nullable=false) + private boolean fromNewLogin; + + @Column(name="TICKET_ALREADY_GRANTED", nullable=false) + private Boolean grantedTicketAlready = Boolean.FALSE; + + /** + * Instantiates a new service ticket impl. + */ + public ServiceTicketImpl() { + // exists for JPA purposes + } + + /** + * Constructs a new ServiceTicket with a Unique Id, a TicketGrantingTicket, + * a Service, Expiration Policy and a flag to determine if the ticket + * creation was from a new Login or not. + * + * @param id the unique identifier for the ticket. + * @param ticket the TicketGrantingTicket parent. + * @param service the service this ticket is for. + * @param fromNewLogin is it from a new login. + * @param policy the expiration policy for the Ticket. + * @throws IllegalArgumentException if the TicketGrantingTicket or the + * Service are null. + */ + protected ServiceTicketImpl(final String id, + @NotNull final TicketGrantingTicketImpl ticket, @NotNull final Service service, + final boolean fromNewLogin, final ExpirationPolicy policy) { + super(id, ticket, policy); + + Assert.notNull(service, "service cannot be null"); + Assert.notNull(ticket, "ticket cannot be null"); + this.service = service; + this.fromNewLogin = fromNewLogin; + } + + public boolean isFromNewLogin() { + return this.fromNewLogin; + } + + public Service getService() { + return this.service; + } + + /** + * {@inheritDoc} + *

The state of the ticket is affected by this operation and the + * ticket will be considered used regardless of the match result. + * The state update subsequently may impact the ticket expiration + * policy in that, depending on the policy configuration, the ticket + * may be considered expired. + */ + @Override + public boolean isValidFor(final Service serviceToValidate) { + updateState(); + return serviceToValidate.matches(this.service); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(final Object object) { + if (object == null) { + return false; + } + if (object == this) { + return true; + } + if (!(object instanceof ServiceTicket)) { + return false; + } + + final Ticket ticket = (Ticket) object; + + return new EqualsBuilder() + .append(ticket.getId(), this.getId()) + .isEquals(); + } + + @Override + public TicketGrantingTicket grantTicketGrantingTicket( + final String id, final Authentication authentication, + final ExpirationPolicy expirationPolicy) { + synchronized (this) { + if(this.grantedTicketAlready) { + throw new IllegalStateException( + "TicketGrantingTicket already generated for this ServiceTicket. Cannot grant more than one TGT for ServiceTicket"); + } + this.grantedTicketAlready = Boolean.TRUE; + } + + return new TicketGrantingTicketImpl(id, service, + this.getGrantingTicket(), authentication, expirationPolicy); + } + + public Authentication getAuthentication() { + return null; + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketCreationException.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketCreationException.java new file mode 100644 index 000000000000..8d811576ce03 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketCreationException.java @@ -0,0 +1,52 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket; + +/** + * Exception thrown if there is an error creating a ticket. + * + * @author Scott Battaglia + + * @since 3.0.0 + */ +public class TicketCreationException extends TicketException { + + /** Serializable ID for unique id. */ + private static final long serialVersionUID = 5501212207531289993L; + + /** Code description. */ + private static final String CODE = "CREATION_ERROR"; + + /** + * Constructs a TicketCreationException with the default exception code. + */ + public TicketCreationException() { + super(CODE); + } + + /** + * Constructs a TicketCreationException with the default exception code and + * the original exception that was thrown. + * + * @param throwable the chained exception + */ + public TicketCreationException(final Throwable throwable) { + super(CODE, throwable); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketGrantingTicketImpl.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketGrantingTicketImpl.java new file mode 100644 index 000000000000..ee6dd6c96702 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketGrantingTicketImpl.java @@ -0,0 +1,276 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket; + +import com.google.common.collect.ImmutableMap; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.Assert; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Lob; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * Concrete implementation of a TicketGrantingTicket. A TicketGrantingTicket is + * the global identifier of a principal into the system. It grants the Principal + * single-sign on access to any service that opts into single-sign on. + * Expiration of a TicketGrantingTicket is controlled by the ExpirationPolicy + * specified as object creation. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +@Entity +@Table(name="TICKETGRANTINGTICKET") +public final class TicketGrantingTicketImpl extends AbstractTicket implements TicketGrantingTicket { + + /** Unique Id for serialization. */ + private static final long serialVersionUID = -8608149809180911599L; + + /** Logger instance. */ + private static final Logger LOGGER = LoggerFactory.getLogger(TicketGrantingTicketImpl.class); + + /** The authenticated object for which this ticket was generated for. */ + @Lob + @Column(name="AUTHENTICATION", nullable=false, length = 1000000) + private Authentication authentication; + + /** Flag to enforce manual expiration. */ + @Column(name="EXPIRED", nullable=false) + private Boolean expired = Boolean.FALSE; + + /** Service that produced a proxy-granting ticket. */ + @Column(name="PROXIED_BY", nullable=true) + private Service proxiedBy; + + /** The services associated to this ticket. */ + @Lob + @Column(name="SERVICES_GRANTED_ACCESS_TO", nullable=false, length = 1000000) + private final HashMap services = new HashMap<>(); + + @Lob + @Column(name="SUPPLEMENTAL_AUTHENTICATIONS", nullable=false, length = 1000000) + private final ArrayList supplementalAuthentications = new ArrayList<>(); + + /** + * Instantiates a new ticket granting ticket impl. + */ + public TicketGrantingTicketImpl() { + // nothing to do + } + + /** + * Constructs a new TicketGrantingTicket. + * May throw an {@link IllegalArgumentException} if the Authentication object is null. + * + * @param id the id of the Ticket + * @param proxiedBy Service that produced this proxy ticket. + * @param parentTicketGrantingTicket the parent ticket + * @param authentication the Authentication request for this ticket + * @param policy the expiration policy for this ticket. + */ + public TicketGrantingTicketImpl(final String id, + final Service proxiedBy, + final TicketGrantingTicket parentTicketGrantingTicket, + @NotNull final Authentication authentication, final ExpirationPolicy policy) { + + super(id, parentTicketGrantingTicket, policy); + + if (parentTicketGrantingTicket != null && proxiedBy == null) { + throw new IllegalArgumentException("Must specify proxiedBy when providing parent TGT"); + } + Assert.notNull(authentication, "authentication cannot be null"); + this.authentication = authentication; + this.proxiedBy = proxiedBy; + } + + /** + * Constructs a new TicketGrantingTicket without a parent + * TicketGrantingTicket. + * + * @param id the id of the Ticket + * @param authentication the Authentication request for this ticket + * @param policy the expiration policy for this ticket. + */ + public TicketGrantingTicketImpl(final String id, + final Authentication authentication, final ExpirationPolicy policy) { + this(id, null, null, authentication, policy); + } + + /** + * {@inheritDoc} + */ + @Override + public Authentication getAuthentication() { + return this.authentication; + } + + /** + * {@inheritDoc} + *

The state of the ticket is affected by this operation and the + * ticket will be considered used. The state update subsequently may + * impact the ticket expiration policy in that, depending on the policy + * configuration, the ticket may be considered expired. + */ + @Override + public synchronized ServiceTicket grantServiceTicket(final String id, + final Service service, final ExpirationPolicy expirationPolicy, + final boolean credentialsProvided) { + final ServiceTicket serviceTicket = new ServiceTicketImpl(id, this, + service, this.getCountOfUses() == 0 || credentialsProvided, + expirationPolicy); + + updateState(); + + final List authentications = getChainedAuthentications(); + service.setPrincipal(authentications.get(authentications.size()-1).getPrincipal()); + + this.services.put(id, service); + + return serviceTicket; + } + + /** + * Gets an immutable map of service ticket and services accessed by this ticket-granting ticket. + * Unlike {@link Collections#unmodifiableMap(java.util.Map)}, + * which is a view of a separate map which can still change, an instance of {@link ImmutableMap} + * contains its own data and will never change. + * + * @return an immutable map of service ticket and services accessed by this ticket-granting ticket. + */ + @Override + public synchronized Map getServices() { + return ImmutableMap.copyOf(this.services); + } + + /** + * Remove all services of the TGT (at logout). + */ + @Override + public void removeAllServices() { + services.clear(); + } + + /** + * Return if the TGT has no parent. + * + * @return if the TGT has no parent. + */ + @Override + public boolean isRoot() { + return this.getGrantingTicket() == null; + } + + /** + * {@inheritDoc} + */ + @Override + public void markTicketExpired() { + this.expired = Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public TicketGrantingTicket getRoot() { + TicketGrantingTicket current = this; + TicketGrantingTicket parent = current.getGrantingTicket(); + while (parent != null) { + current = parent; + parent = current.getGrantingTicket(); + } + return current; + } + + /** + * Return if the TGT is expired. + * + * @return if the TGT is expired. + */ + @Override + public boolean isExpiredInternal() { + return this.expired; + } + + /** + * {@inheritDoc} + */ + @Override + public List getSupplementalAuthentications() { + return this.supplementalAuthentications; + } + + /** + * {@inheritDoc} + */ + @Override + public List getChainedAuthentications() { + final List list = new ArrayList<>(); + + list.add(getAuthentication()); + + if (getGrantingTicket() == null) { + return Collections.unmodifiableList(list); + } + + list.addAll(getGrantingTicket().getChainedAuthentications()); + return Collections.unmodifiableList(list); + } + + @Override + public Service getProxiedBy() { + return this.proxiedBy; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(final Object object) { + if (object == null) { + return false; + } + if (object == this) { + return true; + } + if (!(object instanceof TicketGrantingTicket)) { + return false; + } + + final Ticket ticket = (Ticket) object; + + return new EqualsBuilder() + .append(ticket.getId(), this.getId()) + .isEquals(); + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketValidationException.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketValidationException.java new file mode 100644 index 000000000000..0c18482d3d3f --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketValidationException.java @@ -0,0 +1,65 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket; + +import org.jasig.cas.authentication.principal.Service; + +/** + * Exception to alert that there was an error validating the ticket. + * + * @author Scott Battaglia + * @since 3.0.0 + * @deprecated As of 4.1, the class is required to note its abstractness in the name and will be renamed in the future. + */ +@Deprecated +public abstract class TicketValidationException extends TicketException { + /** The code description. */ + protected static final String CODE = "INVALID_TICKET"; + + /** Unique Serial ID. */ + private static final long serialVersionUID = 3257004341537093175L; + + private final Service service; + + /** + * Constructs a TicketValidationException with the default exception code + * and the original exception that was thrown. + * @param service original service + */ + public TicketValidationException(final Service service) { + this(CODE, service); + } + + /** + * Instantiates a new Ticket validation exception. + * + * @param code the code + * @param service the service + * @since 4.1 + */ + public TicketValidationException(final String code, final Service service) { + super(code); + this.service = service; + } + + public Service getOriginalService() { + return this.service; + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/UnrecognizableServiceForServiceTicketValidationException.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/UnrecognizableServiceForServiceTicketValidationException.java new file mode 100644 index 000000000000..52a97b0c5c96 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/UnrecognizableServiceForServiceTicketValidationException.java @@ -0,0 +1,45 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.ticket; + +import org.jasig.cas.authentication.principal.Service; + +/** + * An exception that may be thrown during service ticket validation + * to indicate that the service ticket is not valid and was not originally + * issued for the submitted service. + * @author Misagh Moayyed + * @since 4.1 + */ +public class UnrecognizableServiceForServiceTicketValidationException extends TicketValidationException { + /** The code description. */ + protected static final String CODE = "INVALID_SERVICE"; + + private static final long serialVersionUID = -8076771862820008358L; + + /** + * Instantiates a new Unrecognizable service for service ticket validation exception. + * + * @param service the service + */ + public UnrecognizableServiceForServiceTicketValidationException(final Service service) { + super(CODE, service); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/UnsatisfiedAuthenticationPolicyException.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/UnsatisfiedAuthenticationPolicyException.java new file mode 100644 index 000000000000..0b9982235f47 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/UnsatisfiedAuthenticationPolicyException.java @@ -0,0 +1,61 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket; + +import org.jasig.cas.authentication.ContextualAuthenticationPolicy; +import org.springframework.util.Assert; + +/** + * Error condition arising at ticket creation or validation time when a ticketing operation relying on authentication + * cannot proceed due to unsatisfied authentication security policy. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class UnsatisfiedAuthenticationPolicyException extends TicketException { + + /** Serializable ID for unique id. */ + private static final long serialVersionUID = -827432780367197133L; + + /** Code description. */ + private static final String CODE = "UNSATISFIED_AUTHN_POLICY"; + + /** Unfulfilled policy that caused this exception. */ + private final ContextualAuthenticationPolicy policy; + + /** + * Creates a new instance with no cause. + * + * @param policy Non-null unfulfilled security policy that caused exception. + */ + public UnsatisfiedAuthenticationPolicyException(final ContextualAuthenticationPolicy policy) { + super(CODE); + Assert.notNull(policy, "ContextualAuthenticationPolicy cannot be null"); + this.policy = policy; + } + + /** + * Gets the unsatisfied policy that caused this exception. + * + * @return Non-null unsatisfied policy cause. + */ + public ContextualAuthenticationPolicy getPolicy() { + return policy; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/package.html b/cas-server-core/src/main/java/org/jasig/cas/ticket/package.html new file mode 100644 index 000000000000..c2e3eb520d8d --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/package.html @@ -0,0 +1,27 @@ + + + + +Classes that represent tickets and can manipulate tickets. + + + diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/proxy/package.html b/cas-server-core/src/main/java/org/jasig/cas/ticket/proxy/package.html new file mode 100644 index 000000000000..b2fb3b068476 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/proxy/package.html @@ -0,0 +1,28 @@ + + + +

This package contains an abstracted interface for handling the +proxying of a user. The abstraction exists because the different +versions of CAS may actually handle the proxying differently but the +workflow of the validation process in the web tier is the same.

+ + diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/proxy/support/Cas10ProxyHandler.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/proxy/support/Cas10ProxyHandler.java new file mode 100644 index 000000000000..9577294ad626 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/proxy/support/Cas10ProxyHandler.java @@ -0,0 +1,44 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.proxy.support; + +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.proxy.ProxyHandler; + +/** + * Dummy ProxyHandler that does nothing. Useful for Cas 1.0 compliance as CAS + * 1.0 has no proxying capabilities. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public final class Cas10ProxyHandler implements ProxyHandler { + + @Override + public String handle(final Credential credential, + final TicketGrantingTicket proxyGrantingTicketId) { + return null; + } + + @Override + public boolean canHandle(final Credential credential) { + return false; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/proxy/support/Cas20ProxyHandler.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/proxy/support/Cas20ProxyHandler.java new file mode 100644 index 000000000000..71024630dfee --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/proxy/support/Cas20ProxyHandler.java @@ -0,0 +1,115 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.proxy.support; + +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.HttpBasedServiceCredential; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.proxy.ProxyHandler; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.jasig.cas.util.http.HttpClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.validation.constraints.NotNull; +import java.net.URL; + +/** + * Proxy Handler to handle the default callback functionality of CAS 2.0. + *

+ * The default behavior as defined in the CAS 2 Specification is to callback the + * URL provided and give it a pgtIou and a pgtId. + *

+ * + * @author Scott Battaglia + * @since 3.0.0 + */ +public final class Cas20ProxyHandler implements ProxyHandler { + private static final int BUFFER_LENGTH_ADDITIONAL_CHARGE = 15; + + /** The proxy granting ticket identifier parameter. */ + private static final String PARAMETER_PROXY_GRANTING_TICKET_IOU = "pgtIou"; + + /** The Constant proxy granting ticket parameter. */ + private static final String PARAMETER_PROXY_GRANTING_TICKET_ID = "pgtId"; + + /** The Commons Logging instance. */ + private final Logger logger = LoggerFactory.getLogger(getClass()); + + /** Generate unique ids. */ + @NotNull + private UniqueTicketIdGenerator uniqueTicketIdGenerator = new DefaultUniqueTicketIdGenerator(); + + /** Instance of Apache Commons HttpClient. */ + @NotNull + private HttpClient httpClient; + + @Override + public String handle(final Credential credential, final TicketGrantingTicket proxyGrantingTicketId) { + final HttpBasedServiceCredential serviceCredentials = (HttpBasedServiceCredential) credential; + final String proxyIou = this.uniqueTicketIdGenerator.getNewTicketId(TicketGrantingTicket.PROXY_GRANTING_TICKET_IOU_PREFIX); + + final URL callbackUrl = serviceCredentials.getCallbackUrl(); + final String serviceCredentialsAsString = callbackUrl.toExternalForm(); + final int bufferLength = serviceCredentialsAsString.length() + proxyIou.length() + + proxyGrantingTicketId.getId().length() + BUFFER_LENGTH_ADDITIONAL_CHARGE; + final StringBuilder stringBuffer = new StringBuilder(bufferLength); + + stringBuffer.append(serviceCredentialsAsString); + + if (callbackUrl.getQuery() != null) { + stringBuffer.append('&'); + } else { + stringBuffer.append('?'); + } + + stringBuffer.append(PARAMETER_PROXY_GRANTING_TICKET_IOU); + stringBuffer.append('='); + stringBuffer.append(proxyIou); + stringBuffer.append('&'); + stringBuffer.append(PARAMETER_PROXY_GRANTING_TICKET_ID); + stringBuffer.append('='); + stringBuffer.append(proxyGrantingTicketId); + + if (this.httpClient.isValidEndPoint(stringBuffer.toString())) { + logger.debug("Sent ProxyIou of {} for service: {}", proxyIou, serviceCredentials); + return proxyIou; + } + + logger.debug("Failed to send ProxyIou of {} for service: {}", proxyIou, serviceCredentials); + return null; + } + + /** + * @param uniqueTicketIdGenerator The uniqueTicketIdGenerator to set. + */ + public void setUniqueTicketIdGenerator(final UniqueTicketIdGenerator uniqueTicketIdGenerator) { + this.uniqueTicketIdGenerator = uniqueTicketIdGenerator; + } + + public void setHttpClient(final HttpClient httpClient) { + this.httpClient = httpClient; + } + + @Override + public boolean canHandle(final Credential credential) { + return true; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/proxy/support/package.html b/cas-server-core/src/main/java/org/jasig/cas/ticket/proxy/support/package.html new file mode 100644 index 000000000000..6480151e923a --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/proxy/support/package.html @@ -0,0 +1,28 @@ + + + + +

Package containing the specific implementations of the ProxyHandler +interface related to the various versions of the CAS protocol.

+ + + diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/AbstractDistributedTicketRegistry.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/AbstractDistributedTicketRegistry.java new file mode 100644 index 000000000000..4ee63f1834b6 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/AbstractDistributedTicketRegistry.java @@ -0,0 +1,269 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; + +import java.util.List; +import java.util.Map; + +/** + * Abstract Implementation that handles some of the commonalities between + * distributed ticket registries. + * + * @author Scott Battaglia + * @since 3.1 + */ +public abstract class AbstractDistributedTicketRegistry extends AbstractTicketRegistry { + + /** + * Update the received ticket. + * + * @param ticket the ticket + */ + protected abstract void updateTicket(final Ticket ticket); + + /** + * Whether or not a callback to the TGT is required when checking for expiration. + * + * @return true, if successful + */ + protected abstract boolean needsCallback(); + + /** + * Gets the proxied ticket instance. + * + * @param ticket the ticket + * @return the proxied ticket instance + */ + protected final Ticket getProxiedTicketInstance(final Ticket ticket) { + if (ticket == null) { + return null; + } + + if (ticket instanceof TicketGrantingTicket) { + return new TicketGrantingTicketDelegator(this, (TicketGrantingTicket) ticket, needsCallback()); + } + + return new ServiceTicketDelegator(this, (ServiceTicket) ticket, needsCallback()); + } + + private static class TicketDelegator implements Ticket { + + private static final long serialVersionUID = 1780193477774123440L; + + private final AbstractDistributedTicketRegistry ticketRegistry; + + private final T ticket; + + private final boolean callback; + + /** + * Instantiates a new ticket delegator. + * + * @param ticketRegistry the ticket registry + * @param ticket the ticket + * @param callback the callback + */ + protected TicketDelegator(final AbstractDistributedTicketRegistry ticketRegistry, + final T ticket, final boolean callback) { + this.ticketRegistry = ticketRegistry; + this.ticket = ticket; + this.callback = callback; + } + + + /** + * Update ticket by the delegated registry. + */ + protected void updateTicket() { + this.ticketRegistry.updateTicket(this.ticket); + } + + protected T getTicket() { + return this.ticket; + } + + public final String getId() { + return this.ticket.getId(); + } + + @Override + public final boolean isExpired() { + if (!callback) { + return this.ticket.isExpired(); + } + + final TicketGrantingTicket t = getGrantingTicket(); + + return this.ticket.isExpired() || (t != null && t.isExpired()); + } + + @Override + public final TicketGrantingTicket getGrantingTicket() { + final TicketGrantingTicket old = this.ticket.getGrantingTicket(); + + if (old == null || !callback) { + return old; + } + + return this.ticketRegistry.getTicket(old.getId(), Ticket.class); + } + + public final long getCreationTime() { + return this.ticket.getCreationTime(); + } + + public final int getCountOfUses() { + return this.ticket.getCountOfUses(); + } + + @Override + public int hashCode() { + return this.ticket.hashCode(); + } + + @Override + public boolean equals(final Object o) { + return this.ticket.equals(o); + } + } + + private static final class ServiceTicketDelegator extends TicketDelegator + implements ServiceTicket { + + private static final long serialVersionUID = 8160636219307822967L; + + /** + * Instantiates a new service ticket delegator. + * + * @param ticketRegistry the ticket registry + * @param serviceTicket the service ticket + * @param callback the callback + */ + protected ServiceTicketDelegator(final AbstractDistributedTicketRegistry ticketRegistry, + final ServiceTicket serviceTicket, final boolean callback) { + super(ticketRegistry, serviceTicket, callback); + } + + @Override + public Service getService() { + return getTicket().getService(); + } + + @Override + public boolean isFromNewLogin() { + return getTicket().isFromNewLogin(); + } + + @Override + public boolean isValidFor(final Service service) { + final boolean b = this.getTicket().isValidFor(service); + updateTicket(); + return b; + } + + @Override + public TicketGrantingTicket grantTicketGrantingTicket(final String id, + final Authentication authentication, final ExpirationPolicy expirationPolicy) { + final TicketGrantingTicket t = this.getTicket().grantTicketGrantingTicket(id, + authentication, expirationPolicy); + updateTicket(); + return t; + } + } + + private static final class TicketGrantingTicketDelegator extends TicketDelegator + implements TicketGrantingTicket { + + private static final long serialVersionUID = 5312560061970601497L; + + /** + * Instantiates a new ticket granting ticket delegator. + * + * @param ticketRegistry the ticket registry + * @param ticketGrantingTicket the ticket granting ticket + * @param callback the callback + */ + protected TicketGrantingTicketDelegator(final AbstractDistributedTicketRegistry ticketRegistry, + final TicketGrantingTicket ticketGrantingTicket, final boolean callback) { + super(ticketRegistry, ticketGrantingTicket, callback); + } + + @Override + public Authentication getAuthentication() { + return getTicket().getAuthentication(); + } + + @Override + public Service getProxiedBy() { + return getTicket().getProxiedBy(); + } + + @Override + public List getSupplementalAuthentications() { + return getTicket().getSupplementalAuthentications(); + } + + @Override + public ServiceTicket grantServiceTicket(final String id, final Service service, + final ExpirationPolicy expirationPolicy, final boolean credentialsProvided) { + final ServiceTicket t = this.getTicket().grantServiceTicket(id, service, + expirationPolicy, credentialsProvided); + updateTicket(); + return t; + } + + @Override + public void markTicketExpired() { + this.getTicket().markTicketExpired(); + updateTicket(); + } + + @Override + public boolean isRoot() { + return getTicket().isRoot(); + } + + @Override + public TicketGrantingTicket getRoot() { + return getTicket().getRoot(); + } + + @Override + public List getChainedAuthentications() { + return getTicket().getChainedAuthentications(); + } + + @Override + public Map getServices() { + return this.getTicket().getServices(); + } + + @Override + public void removeAllServices() { + this.getTicket().removeAllServices(); + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/AbstractTicketRegistry.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/AbstractTicketRegistry.java new file mode 100644 index 000000000000..9783aa9dcb90 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/AbstractTicketRegistry.java @@ -0,0 +1,78 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import org.jasig.cas.monitor.TicketRegistryState; +import org.jasig.cas.ticket.Ticket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.Assert; + +/** + * @author Scott Battaglia + * @since 3.0.0.4 + *

+ * This is a published and supported CAS Server 3 API. + *

+ */ +public abstract class AbstractTicketRegistry implements TicketRegistry, TicketRegistryState { + + /** The Slf4j logger instance. */ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + /** + * {@inheritDoc} + * @throws IllegalArgumentException if class is null. + * @throws ClassCastException if class does not match requested ticket + * class. + * @return specified ticket from the registry + */ + @Override + public final T getTicket(final String ticketId, final Class clazz) { + Assert.notNull(clazz, "clazz cannot be null"); + + final Ticket ticket = this.getTicket(ticketId); + + if (ticket == null) { + return null; + } + + if (!clazz.isAssignableFrom(ticket.getClass())) { + throw new ClassCastException("Ticket [" + ticket.getId() + + " is of type " + ticket.getClass() + + " when we were expecting " + clazz); + } + + return (T) ticket; + } + + @Override + public int sessionCount() { + logger.debug("sessionCount() operation is not implemented by the ticket registry instance {}. Returning unknown as {}", + this.getClass().getName(), Integer.MIN_VALUE); + return Integer.MIN_VALUE; + } + + @Override + public int serviceTicketCount() { + logger.debug("serviceTicketCount() operation is not implemented by the ticket registry instance {}. Returning unknown as {}", + this.getClass().getName(), Integer.MIN_VALUE); + return Integer.MIN_VALUE; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/DefaultTicketRegistry.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/DefaultTicketRegistry.java new file mode 100644 index 000000000000..eee74f8f1634 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/DefaultTicketRegistry.java @@ -0,0 +1,128 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.springframework.util.Assert; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Implementation of the TicketRegistry that is backed by a ConcurrentHashMap. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public final class DefaultTicketRegistry extends AbstractTicketRegistry { + + /** A HashMap to contain the tickets. */ + private final Map cache; + + /** + * Instantiates a new default ticket registry. + */ + public DefaultTicketRegistry() { + this.cache = new ConcurrentHashMap<>(); + } + + /** + * Creates a new, empty registry with the specified initial capacity, load + * factor, and concurrency level. + * + * @param initialCapacity - the initial capacity. The implementation + * performs internal sizing to accommodate this many elements. + * @param loadFactor - the load factor threshold, used to control resizing. + * Resizing may be performed when the average number of elements per bin + * exceeds this threshold. + * @param concurrencyLevel - the estimated number of concurrently updating + * threads. The implementation performs internal sizing to try to + * accommodate this many threads. + */ + public DefaultTicketRegistry(final int initialCapacity, final float loadFactor, final int concurrencyLevel) { + this.cache = new ConcurrentHashMap<>(initialCapacity, loadFactor, concurrencyLevel); + } + + /** + * {@inheritDoc} + * @throws IllegalArgumentException if the Ticket is null. + */ + @Override + public void addTicket(final Ticket ticket) { + Assert.notNull(ticket, "ticket cannot be null"); + + logger.debug("Added ticket [{}] to registry.", ticket.getId()); + this.cache.put(ticket.getId(), ticket); + } + + @Override + public Ticket getTicket(final String ticketId) { + if (ticketId == null) { + return null; + } + + logger.debug("Attempting to retrieve ticket [{}]", ticketId); + final Ticket ticket = this.cache.get(ticketId); + + if (ticket != null) { + logger.debug("Ticket [{}] found in registry.", ticketId); + } + + return ticket; + } + + @Override + public boolean deleteTicket(final String ticketId) { + if (ticketId == null) { + return false; + } + logger.debug("Removing ticket [{}] from registry", ticketId); + return (this.cache.remove(ticketId) != null); + } + + public Collection getTickets() { + return Collections.unmodifiableCollection(this.cache.values()); + } + + @Override + public int sessionCount() { + int count = 0; + for (final Ticket t : this.cache.values()) { + if (t instanceof TicketGrantingTicket) { + count++; + } + } + return count; + } + + @Override + public int serviceTicketCount() { + int count = 0; + for (final Ticket t : this.cache.values()) { + if (t instanceof ServiceTicket) { + count++; + } + } + return count; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/package.html b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/package.html new file mode 100644 index 000000000000..1c7d75aef384 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/package.html @@ -0,0 +1,29 @@ + + + + +

This package contains the classes related to maintaining the +persistance of the Tickets for retrieval later by the Central +Authentication Service.

+ + + diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/DefaultTicketRegistryCleaner.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/DefaultTicketRegistryCleaner.java new file mode 100644 index 000000000000..05ee64a1a794 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/DefaultTicketRegistryCleaner.java @@ -0,0 +1,180 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry.support; + +import org.apache.commons.collections4.Predicate; +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.logout.LogoutManager; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.registry.RegistryCleaner; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.validation.constraints.NotNull; +import java.util.Collection; +import java.util.Collections; + +/** + * The default ticket registry cleaner scans the entire CAS ticket registry + * for expired tickets and removes them. This process is only required so that + * the size of the ticket registry will not grow beyond a reasonable size. + * The functionality of CAS is not dependent on a ticket being removed as soon + * as it is expired. + *

NEW in 3.3.6:

+ *

+ * Locking strategies may be used to support high availability environments. + * In a clustered CAS environment with several CAS nodes executing ticket + * cleanup, it is desirable to execute cleanup from only one CAS node at a time. + * This dramatically reduces the potential for deadlocks in + * JPA-backed ticket registries, for example. + * + * By default this implementation uses {@link NoOpLockingStrategy} to preserve + * the same semantics as previous versions, but specific locking strategies + * for JPA should be used with a JPA-backed ticket registry + * in a clustered CAS environment. + *

+ *

The following property is required.

+ *
    + *
  • ticketRegistry - CAS ticket registry.
  • + *
+ * + * @author Scott Battaglia + * @author Marvin S. Addison + * @author Misagh Moayyed + * @see NoOpLockingStrategy + * @since 3.0.0 + */ +public final class DefaultTicketRegistryCleaner implements RegistryCleaner { + + /** The Commons Logging instance. */ + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @NotNull + private final CentralAuthenticationService centralAuthenticationService; + + /** Execution locking strategy. */ + @NotNull + private LockingStrategy lock = new NoOpLockingStrategy(); + + @NotNull + private TicketRegistry ticketRegistry; + + /** + * Instantiates a new Default ticket registry cleaner. + * + * @param centralAuthenticationService the CAS interface acting as the service layer + * @param ticketRegistry the ticket registry + */ + public DefaultTicketRegistryCleaner(final CentralAuthenticationService centralAuthenticationService, + final TicketRegistry ticketRegistry) { + this.centralAuthenticationService = centralAuthenticationService; + this.ticketRegistry = ticketRegistry; + } + + @Override + public Collection clean() { + try { + logger.info("Beginning ticket cleanup."); + logger.debug("Attempting to acquire ticket cleanup lock."); + if (!this.lock.acquire()) { + logger.info("Could not obtain lock. Aborting cleanup."); + return Collections.emptyList(); + } + logger.debug("Acquired lock. Proceeding with cleanup."); + + final Collection ticketsToRemove = this.centralAuthenticationService.getTickets(new Predicate() { + @Override + public boolean evaluate(final Object o) { + final Ticket ticket = (Ticket) o; + return ticket.isExpired(); + } + }); + + logger.info("{} expired tickets found to be removed.", ticketsToRemove.size()); + + try { + for (final Ticket ticket : ticketsToRemove) { + if (ticket instanceof TicketGrantingTicket) { + logger.debug("Cleaning up expired ticket-granting ticket [{}]", ticket.getId()); + this.centralAuthenticationService.destroyTicketGrantingTicket(ticket.getId()); + } else if (ticket instanceof ServiceTicket) { + logger.debug("Cleaning up expired service ticket [{}]", ticket.getId()); + this.ticketRegistry.deleteTicket(ticket.getId()); + } else { + logger.warn("Unknown ticket type [{} found to clean", ticket.getClass().getSimpleName()); + } + } + } catch (final Exception e) { + logger.error(e.getMessage(), e); + } + + return ticketsToRemove; + } finally { + logger.debug("Releasing ticket cleanup lock."); + this.lock.release(); + logger.info("Finished ticket cleanup."); + } + } + + /** + * @param ticketRegistry The ticketRegistry to set. + * @deprecated As of 4.1. Consider using constructors instead. + */ + @Deprecated + public void setTicketRegistry(final TicketRegistry ticketRegistry) { + logger.warn("Invoking setTicketRegistry() is deprecated and has no impact."); + } + + + /** + * @param strategy Ticket cleanup locking strategy. An exclusive locking + * strategy is preferable if not required for some ticket backing stores, + * such as JPA, in a clustered CAS environment. Use JPA locking strategies + * for JPA-backed ticket registries in a clustered + * CAS environment. + * @deprecated As of 4.1. Consider using constructors instead. + */ + @Deprecated + public void setLock(final LockingStrategy strategy) { + this.lock = strategy; + } + + /** + * @deprecated As of 4.1, single signout callbacks are entirely controlled by the {@link LogoutManager}. + * @param logUserOutOfServices whether to logger the user out of services or not. + */ + @Deprecated + public void setLogUserOutOfServices(final boolean logUserOutOfServices) { + logger.warn("Invoking setLogUserOutOfServices() is deprecated and has no impact."); + } + + /** + * Set the logout manager. + * + * @param logoutManager the logout manager. + * @deprecated As of 4.1. Consider using constructors instead. + */ + @Deprecated + public void setLogoutManager(final LogoutManager logoutManager) { + logger.warn("Invoking setLogoutManager() is deprecated and has no impact."); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/NoOpLockingStrategy.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/NoOpLockingStrategy.java new file mode 100644 index 000000000000..38d23760aec4 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/NoOpLockingStrategy.java @@ -0,0 +1,49 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry.support; + + +/** + * No-Op locking strategy that allows the use of {@link DefaultTicketRegistryCleaner} + * in environments where exclusive access to the registry for cleaning is either + * unnecessary or not possible. + * + * @author Marvin Addison + * @since 3.3.6 + * + */ +public class NoOpLockingStrategy implements LockingStrategy { + + /** + * {@inheritDoc} + * @see org.jasig.cas.ticket.registry.support.LockingStrategy#acquire() + */ + @Override + public boolean acquire() { + return true; + } + + /** + * @see org.jasig.cas.ticket.registry.support.LockingStrategy#release() + */ + public void release() { + // Nothing to release + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/package.html b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/package.html new file mode 100644 index 000000000000..a4b8514e2920 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/package.html @@ -0,0 +1,30 @@ + + + + +

This package contains the supporting versions of the interfaces +defined in the ticket package. Specifically, there are alternative +implementations of the TicketRegistry (such as the EhCache backed one) +and the Registry Cleaner.

+ + + diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/support/AbstractCasExpirationPolicy.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/AbstractCasExpirationPolicy.java new file mode 100644 index 000000000000..ba7e094902c5 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/AbstractCasExpirationPolicy.java @@ -0,0 +1,62 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.ticket.support; + +import org.jasig.cas.ticket.ExpirationPolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; + +/** + * This is an {@link org.jasig.cas.ticket.support.AbstractCasExpirationPolicy} + * that serves as the root parent for all CAS expiration policies + * and exposes a few internal helper methods to children can access + * to objects like the request, etc. + * + * @author Misagh Moayyed mmoayyed@unicon.net + * @since 4.1 + */ +public abstract class AbstractCasExpirationPolicy implements ExpirationPolicy { + + private static final long serialVersionUID = 8042104336580063690L; + + /** The Logger instance shared by all children of this class. */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * Gets the http request based on the + * {@link org.springframework.web.context.request.RequestContextHolder}. + * @return the request or null + */ + protected final HttpServletRequest getRequest() { + try { + final ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); + if (attrs != null) { + return attrs.getRequest(); + } + } catch (final Exception e) { + logger.trace("Unable to obtain the http request", e); + } + return null; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/support/HardTimeoutExpirationPolicy.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/HardTimeoutExpirationPolicy.java new file mode 100644 index 000000000000..6e7bbe12a6dc --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/HardTimeoutExpirationPolicy.java @@ -0,0 +1,69 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.ticket.TicketState; + +import java.util.concurrent.TimeUnit; + +/** + * Ticket expiration policy based on a hard timeout from ticket creation time rather than the + * "idle" timeout provided by {@link org.jasig.cas.ticket.support.TimeoutExpirationPolicy}. + * + * @author Andrew Feller + * @since 3.1.2 + */ +public final class HardTimeoutExpirationPolicy extends AbstractCasExpirationPolicy { + + /** Serialization support. */ + private static final long serialVersionUID = 6728077010285422290L; + + /** The time to kill in milliseconds. */ + private final long timeToKillInMilliSeconds; + + /** No-arg constructor for serialization support. */ + private HardTimeoutExpirationPolicy() { + this.timeToKillInMilliSeconds = 0; + } + + /** + * Instantiates a new hard timeout expiration policy. + * + * @param timeToKillInMilliSeconds the time to kill in milli seconds + */ + public HardTimeoutExpirationPolicy(final long timeToKillInMilliSeconds) { + this.timeToKillInMilliSeconds = timeToKillInMilliSeconds; + } + + /** + * Instantiates a new Hard timeout expiration policy. + * + * @param timeToKill the time to kill + * @param timeUnit the time unit + */ + public HardTimeoutExpirationPolicy(final long timeToKill, final TimeUnit timeUnit) { + this.timeToKillInMilliSeconds = timeUnit.toMillis(timeToKill); + } + + @Override + public boolean isExpired(final TicketState ticketState) { + return (ticketState == null) + || (System.currentTimeMillis() - ticketState.getCreationTime() >= this.timeToKillInMilliSeconds); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/support/MultiTimeUseOrTimeoutExpirationPolicy.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/MultiTimeUseOrTimeoutExpirationPolicy.java new file mode 100644 index 000000000000..e903fc0b00b5 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/MultiTimeUseOrTimeoutExpirationPolicy.java @@ -0,0 +1,84 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.ticket.TicketState; +import org.springframework.util.Assert; + +import java.util.concurrent.TimeUnit; + +/** + * ExpirationPolicy that is based on certain number of uses of a ticket or a + * certain time period for a ticket to exist. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public final class MultiTimeUseOrTimeoutExpirationPolicy extends AbstractCasExpirationPolicy { + + /** Serialization support. */ + private static final long serialVersionUID = -5704993954986738308L; + + /** The time to kill in milliseconds. */ + private final long timeToKillInMilliSeconds; + + /** The maximum number of uses before expiration. */ + private final int numberOfUses; + + + /** No-arg constructor for serialization support. */ + private MultiTimeUseOrTimeoutExpirationPolicy() { + this.timeToKillInMilliSeconds = 0; + this.numberOfUses = 0; + } + + /** + * Instantiates a new multi time use or timeout expiration policy. + * + * @param numberOfUses the number of uses + * @param timeToKillInMilliSeconds the time to kill in milli seconds + */ + public MultiTimeUseOrTimeoutExpirationPolicy(final int numberOfUses, + final long timeToKillInMilliSeconds) { + this.timeToKillInMilliSeconds = timeToKillInMilliSeconds; + this.numberOfUses = numberOfUses; + Assert.isTrue(this.numberOfUses > 0, "numberOfUsers must be greater than 0."); + Assert.isTrue(this.timeToKillInMilliSeconds > 0, "timeToKillInMilliseconds must be greater than 0."); + + } + + /** + * Instantiates a new multi time use or timeout expiration policy. + * + * @param numberOfUses the number of uses + * @param timeToKill the time to kill + * @param timeUnit the time unit + */ + public MultiTimeUseOrTimeoutExpirationPolicy(final int numberOfUses, final long timeToKill, + final TimeUnit timeUnit) { + this(numberOfUses, timeUnit.toMillis(timeToKill)); + } + + @Override + public boolean isExpired(final TicketState ticketState) { + return (ticketState == null) + || (ticketState.getCountOfUses() >= this.numberOfUses) + || (System.currentTimeMillis() - ticketState.getLastTimeUsed() >= this.timeToKillInMilliSeconds); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/support/NeverExpiresExpirationPolicy.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/NeverExpiresExpirationPolicy.java new file mode 100644 index 000000000000..5c7a52c32c82 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/NeverExpiresExpirationPolicy.java @@ -0,0 +1,40 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.ticket.TicketState; + +/** + * NeverExpiresExpirationPolicy always answers false when asked if a Ticket is + * expired. Use this policy when you want a Ticket to live forever, or at least + * as long as the particular CAS Universe exists. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public final class NeverExpiresExpirationPolicy extends AbstractCasExpirationPolicy { + + /** Serializable Unique ID. */ + private static final long serialVersionUID = 3833747698242303540L; + + @Override + public boolean isExpired(final TicketState ticketState) { + return false; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/support/RememberMeDelegatingExpirationPolicy.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/RememberMeDelegatingExpirationPolicy.java new file mode 100644 index 000000000000..fa2e7a4b95c0 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/RememberMeDelegatingExpirationPolicy.java @@ -0,0 +1,66 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.authentication.RememberMeCredential; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.TicketState; + +import javax.validation.constraints.NotNull; + +/** + * Delegates to different expiration policies depending on whether remember me + * is true or not. + * + * @author Scott Battaglia + * @since 3.2.1 + * + */ +public final class RememberMeDelegatingExpirationPolicy extends AbstractCasExpirationPolicy { + + /** Serialization support. */ + private static final long serialVersionUID = -2735975347698196127L; + + @NotNull + private ExpirationPolicy rememberMeExpirationPolicy; + + @NotNull + private ExpirationPolicy sessionExpirationPolicy; + + @Override + public boolean isExpired(final TicketState ticketState) { + final Boolean b = (Boolean) ticketState.getAuthentication().getAttributes(). + get(RememberMeCredential.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME); + + if (b == null || b.equals(Boolean.FALSE)) { + return this.sessionExpirationPolicy.isExpired(ticketState); + } + + return this.rememberMeExpirationPolicy.isExpired(ticketState); + } + + public void setRememberMeExpirationPolicy( + final ExpirationPolicy rememberMeExpirationPolicy) { + this.rememberMeExpirationPolicy = rememberMeExpirationPolicy; + } + + public void setSessionExpirationPolicy(final ExpirationPolicy sessionExpirationPolicy) { + this.sessionExpirationPolicy = sessionExpirationPolicy; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/support/ThrottledUseAndTimeoutExpirationPolicy.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/ThrottledUseAndTimeoutExpirationPolicy.java new file mode 100644 index 000000000000..1c9f8c766679 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/ThrottledUseAndTimeoutExpirationPolicy.java @@ -0,0 +1,75 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.ticket.TicketState; + +/** + * Implementation of an expiration policy that adds the concept of saying that a + * ticket can only be used once every X milliseconds to prevent misconfigured + * clients from consuming resources by doing constant redirects. + * + * @author Scott Battaglia + * @since 3.0.0.5 + */ +public final class ThrottledUseAndTimeoutExpirationPolicy extends AbstractCasExpirationPolicy { + + /** Serialization support. */ + private static final long serialVersionUID = 205979491183779408L; + + /** The time to kill in milliseconds. */ + private long timeToKillInMilliSeconds; + + /** Time time between which a ticket must wait to be used again. */ + private long timeInBetweenUsesInMilliSeconds; + + public void setTimeInBetweenUsesInMilliSeconds( + final long timeInBetweenUsesInMilliSeconds) { + this.timeInBetweenUsesInMilliSeconds = timeInBetweenUsesInMilliSeconds; + } + + public void setTimeToKillInMilliSeconds(final long timeToKillInMilliSeconds) { + this.timeToKillInMilliSeconds = timeToKillInMilliSeconds; + } + + @Override + public boolean isExpired(final TicketState ticketState) { + final long currentTimeInMillis = System.currentTimeMillis(); + final long lastTimeTicketWasUsed = ticketState.getLastTimeUsed(); + + if (ticketState.getCountOfUses() == 0 + && (currentTimeInMillis - lastTimeTicketWasUsed < this.timeToKillInMilliSeconds)) { + logger.debug("Ticket is not expired due to a count of zero and the time being less " + + "than the timeToKillInMilliseconds"); + return false; + } + + if ((currentTimeInMillis - lastTimeTicketWasUsed >= this.timeToKillInMilliSeconds)) { + logger.debug("Ticket is expired due to the time being greater than the timeToKillInMilliseconds"); + return true; + } + + if ((currentTimeInMillis - lastTimeTicketWasUsed <= this.timeInBetweenUsesInMilliSeconds)) { + logger.warn("Ticket is expired due to the time being less than the waiting period."); + return true; + } + + return false; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/support/TicketGrantingTicketExpirationPolicy.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/TicketGrantingTicketExpirationPolicy.java new file mode 100644 index 000000000000..b65682ddce82 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/TicketGrantingTicketExpirationPolicy.java @@ -0,0 +1,138 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.ticket.TicketState; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; + +import java.util.concurrent.TimeUnit; + +/** + * Provides the Ticket Granting Ticket expiration policy. Ticket Granting Tickets + * can be used any number of times, have a fixed lifetime, and an idle timeout. + * + * @author William G. Thompson, Jr. + * @since 3.4.10 + */ +public final class TicketGrantingTicketExpirationPolicy extends AbstractCasExpirationPolicy implements InitializingBean { + + /** Serialization support. */ + private static final long serialVersionUID = 7670537200691354820L; + + /** Maximum time this ticket is valid. */ + private long maxTimeToLiveInMilliSeconds; + + /** Time to kill in milliseconds. */ + private long timeToKillInMilliSeconds; + + /** + * @deprecated As of 4.1. + * Instantiates a new Ticket granting ticket expiration policy. + */ + @Deprecated + public TicketGrantingTicketExpirationPolicy() { + this.maxTimeToLiveInMilliSeconds = 0; + this.timeToKillInMilliSeconds = 0; + } + + /** + * Instantiates a new Ticket granting ticket expiration policy. + * + * @param maxTimeToLive the max time to live + * @param timeToKill the time to kill + * @param timeUnit the time unit + */ + public TicketGrantingTicketExpirationPolicy(final long maxTimeToLive, final long timeToKill, final TimeUnit timeUnit) { + this.maxTimeToLiveInMilliSeconds = timeUnit.toMillis(maxTimeToLive); + this.timeToKillInMilliSeconds = timeUnit.toMillis(timeToKill); + } + + /** + * @deprecated As of 4.1. + * Set max time to live in milli seconds. + * + * @param maxTimeToLiveInMilliSeconds the max time to live in milli seconds + */ + @Deprecated + public void setMaxTimeToLiveInMilliSeconds(final long maxTimeToLiveInMilliSeconds){ + this.maxTimeToLiveInMilliSeconds = maxTimeToLiveInMilliSeconds; + } + + /** + * @deprecated As of 4.1. + * Sets time to kill in milli seconds. + * + * @param timeToKillInMilliSeconds the time to kill in milli seconds + */ + @Deprecated + public void setTimeToKillInMilliSeconds(final long timeToKillInMilliSeconds) { + this.timeToKillInMilliSeconds = timeToKillInMilliSeconds; + } + + /** + * @deprecated As of 4.1. + * Convenient virtual property setter to set time in seconds. + * @param maxTimeToLiveInSeconds max number of seconds for the tickets to stay alive + **/ + @Deprecated + public void setMaxTimeToLiveInSeconds(final long maxTimeToLiveInSeconds){ + if(this.maxTimeToLiveInMilliSeconds == 0L) { + this.maxTimeToLiveInMilliSeconds = TimeUnit.SECONDS.toMillis(maxTimeToLiveInSeconds); + } + } + + /** + * @deprecated As of 4.1. + * @param timeToKillInSeconds time for the ticket to stay active in seconds + * Convenient virtual property setter to set time in seconds. + **/ + @Deprecated + public void setTimeToKillInSeconds(final long timeToKillInSeconds) { + if(this.timeToKillInMilliSeconds == 0L) { + this.timeToKillInMilliSeconds = TimeUnit.SECONDS.toMillis(timeToKillInSeconds); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + Assert.isTrue((maxTimeToLiveInMilliSeconds >= timeToKillInMilliSeconds), + "maxTimeToLiveInMilliSeconds must be greater than or equal to timeToKillInMilliSeconds."); + } + + @Override + public boolean isExpired(final TicketState ticketState) { + final long currentSystemTimeInMillis = System.currentTimeMillis(); + + // Ticket has been used, check maxTimeToLive (hard window) + if ((currentSystemTimeInMillis - ticketState.getCreationTime() >= maxTimeToLiveInMilliSeconds)) { + logger.debug("Ticket is expired because the time since creation is greater than maxTimeToLiveInMilliSeconds"); + return true; + } + + // Ticket is within hard window, check timeToKill (sliding window) + if ((currentSystemTimeInMillis - ticketState.getLastTimeUsed() >= timeToKillInMilliSeconds)) { + logger.debug("Ticket is expired because the time since last use is greater than timeToKillInMilliseconds"); + return true; + } + + return false; + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/support/TimeoutExpirationPolicy.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/TimeoutExpirationPolicy.java new file mode 100644 index 000000000000..7fcd461c81d3 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/TimeoutExpirationPolicy.java @@ -0,0 +1,74 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.ticket.TicketState; + +import java.util.concurrent.TimeUnit; + +/** + * Expiration policy that is based on a certain time period for a ticket to + * exist. + *

+ * The expiration policy defined by this class is one of inactivity. If you are inactive for the specified + * amount of time, the ticket will be expired. + * + * @author Scott Battaglia + + * @since 3.0.0 + */ +public final class TimeoutExpirationPolicy extends AbstractCasExpirationPolicy { + + /** Serialization support. */ + private static final long serialVersionUID = -7636642464326939536L; + + /** The time to kill in milliseconds. */ + private final long timeToKillInMilliSeconds; + + + /** No-arg constructor for serialization support. */ + private TimeoutExpirationPolicy() { + this.timeToKillInMilliSeconds = 0; + } + + /** + * Instantiates a new timeout expiration policy. + * + * @param timeToKillInMilliSeconds the time to kill in milli seconds + */ + public TimeoutExpirationPolicy(final long timeToKillInMilliSeconds) { + this.timeToKillInMilliSeconds = timeToKillInMilliSeconds; + } + + /** + * Instantiates a new Timeout expiration policy. + * + * @param timeToKill the time to kill + * @param timeUnit the time unit + */ + public TimeoutExpirationPolicy(final long timeToKill, final TimeUnit timeUnit) { + this.timeToKillInMilliSeconds = timeUnit.toMillis(timeToKill); + } + + @Override + public boolean isExpired(final TicketState ticketState) { + return (ticketState == null) + || (System.currentTimeMillis() - ticketState.getLastTimeUsed() >= this.timeToKillInMilliSeconds); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/support/package.html b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/package.html new file mode 100644 index 000000000000..4b906305970f --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/package.html @@ -0,0 +1,31 @@ + + + + +

This package includes the various default expiration policies +included with CAS. A ticket is given an expiration policy such that if +you ask a ticket if it is expired, it will check itself against the +expiration policy and then determine what response to give.

+

Current implementations include Never Expires, Number of Uses, and a +Time Out policy.

+ + diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/AbstractJacksonBackedJsonSerializer.java b/cas-server-core/src/main/java/org/jasig/cas/util/AbstractJacksonBackedJsonSerializer.java new file mode 100644 index 000000000000..381aa7ee6059 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/AbstractJacksonBackedJsonSerializer.java @@ -0,0 +1,172 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.PrettyPrinter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; + +/** + * Generic class to serialize objects to/from JSON based on jackson. + * @author Misagh Moayyed + * @since 4.1 + */ +public abstract class AbstractJacksonBackedJsonSerializer implements JsonSerializer { + private static final long serialVersionUID = -8415599777321259365L; + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractJacksonBackedJsonSerializer.class); + + /** + * The Pretty printer. + */ + private final PrettyPrinter prettyPrinter; + + /** + * The Object mapper. + */ + private final ObjectMapper objectMapper; + + /** + * Instantiates a new Registered service json serializer. + * Uses the {@link com.fasterxml.jackson.core.util.DefaultPrettyPrinter} for formatting. + */ + public AbstractJacksonBackedJsonSerializer() { + this(new DefaultPrettyPrinter()); + } + + /** + * Instantiates a new Registered service json serializer. + * + * @param prettyPrinter the pretty printer + */ + public AbstractJacksonBackedJsonSerializer(final PrettyPrinter prettyPrinter) { + this.objectMapper = initializeObjectMapper(); + this.prettyPrinter = prettyPrinter; + } + + /** + * Instantiates a new Registered service json serializer. + * + * @param objectMapper the object mapper + * @param prettyPrinter the pretty printer + */ + public AbstractJacksonBackedJsonSerializer(final ObjectMapper objectMapper, final PrettyPrinter prettyPrinter) { + this.objectMapper = objectMapper; + this.prettyPrinter = prettyPrinter; + } + + @Override + public T fromJson(final String json) { + try { + return this.objectMapper.readValue(json, getTypeToSerialize()); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public T fromJson(final File json) { + try { + return this.objectMapper.readValue(json, getTypeToSerialize()); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public T fromJson(final Reader json) { + try { + return this.objectMapper.readValue(json, getTypeToSerialize()); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public T fromJson(final InputStream json) { + try { + return this.objectMapper.readValue(json, getTypeToSerialize()); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void toJson(final OutputStream out, final T object) { + try { + this.objectMapper.writer(this.prettyPrinter).writeValue(out, object); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void toJson(final Writer out, final T object) { + try { + this.objectMapper.writer(this.prettyPrinter).writeValue(out, object); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void toJson(final File out, final T object) { + try { + this.objectMapper.writer(this.prettyPrinter).writeValue(out, object); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Initialize object mapper. + * + * @return the object mapper + */ + protected ObjectMapper initializeObjectMapper() { + final ObjectMapper mapper = new ObjectMapper(); + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + mapper.setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC); + mapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC); + mapper.setVisibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC); + mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + return mapper; + } + + /** + * Gets type to serialize. + * + * @return the type to serialize + */ + protected abstract Class getTypeToSerialize(); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/AopUtils.java b/cas-server-core/src/main/java/org/jasig/cas/util/AopUtils.java new file mode 100644 index 000000000000..5a7c2695ab81 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/AopUtils.java @@ -0,0 +1,51 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import org.aspectj.lang.JoinPoint; + + +/** + * Utility class to assist with AOP operations. + * + * @author Marvin S. Addison + * @since 3.4 + * + */ +public final class AopUtils { + + /** + * Instantiates a new aop utils. + */ + private AopUtils() {} + + /** + * Unwraps a join point that may be nested due to layered proxies. + * + * @param point Join point to unwrap. + * @return Innermost join point; if not nested, simply returns the argument. + */ + public static JoinPoint unWrapJoinPoint(final JoinPoint point) { + JoinPoint naked = point; + while (naked.getArgs().length > 0 && naked.getArgs()[0] instanceof JoinPoint) { + naked = (JoinPoint) naked.getArgs()[0]; + } + return naked; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/ApplicationContextProvider.java b/cas-server-core/src/main/java/org/jasig/cas/util/ApplicationContextProvider.java new file mode 100644 index 000000000000..4aa43e063df7 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/ApplicationContextProvider.java @@ -0,0 +1,40 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * @author Misagh Moayyed + * An implementation of {@link ApplicationContextAware} that statically + * holds the application context + * @since 3.0.0. + */ +public final class ApplicationContextProvider implements ApplicationContextAware { + private static ApplicationContext CONTEXT; + + public static ApplicationContext getApplicationContext() { + return CONTEXT; + } + + public void setApplicationContext(final ApplicationContext ctx) { + CONTEXT = ctx; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/CipherExecutor.java b/cas-server-core/src/main/java/org/jasig/cas/util/CipherExecutor.java new file mode 100644 index 000000000000..df77e03f521c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/CipherExecutor.java @@ -0,0 +1,47 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.util; + +import javax.validation.constraints.NotNull; + +/** + * Responsible to define operation that deal with encryption, signing + * and verification of a value. + * + * @author Misagh Moayyed + * @since 4.1 + */ +public interface CipherExecutor { + + /** + * Encrypt the value. Implementations may + * choose to also sign the final value. + * @param value the value + * @return the encrypted value or null + */ + String encode(@NotNull String value); + + /** + * Decode the value. Signatures may also be verified. + * @param value encrypted value + * @return the decoded value. + */ + String decode(@NotNull String value); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/CompressionUtils.java b/cas-server-core/src/main/java/org/jasig/cas/util/CompressionUtils.java new file mode 100644 index 000000000000..4a747dcabb67 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/CompressionUtils.java @@ -0,0 +1,184 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.zip.Deflater; +import java.util.zip.InflaterInputStream; +import java.util.zip.InflaterOutputStream; + +/** + * This is {@link CompressionUtils} + * that encapsulates common base64 calls and operations + * in one spot. + * + * @author Misagh Moayyed mmoayyed@unicon.net + * @since 4.1 + */ +public final class CompressionUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(CompressionUtils.class); + + private static final int INFLATED_ARRAY_LENGTH = 10000; + + private static final String UTF8_ENCODING = "UTF-8"; + + /** + * Private ctor for a utility class. + */ + private CompressionUtils() { + } + + /** + * Inflate the given byte array by {@link #INFLATED_ARRAY_LENGTH}. + * + * @param bytes the bytes + * @return the array as a string with UTF-8 encoding + */ + public static String inflate(final byte[] bytes) { + try (final ByteArrayInputStream inb = new ByteArrayInputStream(bytes); + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final InflaterOutputStream ios = new InflaterOutputStream(out);) { + IOUtils.copy(inb, ios); + return new String(out.toByteArray(), UTF8_ENCODING); + } catch (final Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + /** + * Deflate the given bytes using zlib. + * The result will be base64 encoded with {@link #UTF8_ENCODING}. + * + * @param bytes the bytes + * @return the converted string + */ + public static String deflate(final byte[] bytes) { + final String data = new String(bytes, Charset.forName(UTF8_ENCODING)); + return deflate(data); + } + + /** + * Deflate the given string via a {@link java.util.zip.Deflater}. + * The result will be base64 encoded with {@link #UTF8_ENCODING}. + * + * @param data the data + * @return base64 encoded string + */ + public static String deflate(final String data) { + try { + final Deflater deflater = new Deflater(); + deflater.setInput(data.getBytes(UTF8_ENCODING)); + deflater.finish(); + final byte[] buffer = new byte[data.length()]; + final int resultSize = deflater.deflate(buffer); + final byte[] output = new byte[resultSize]; + System.arraycopy(buffer, 0, output, 0, resultSize); + return encodeBase64(output); + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException("Cannot find encoding:" + UTF8_ENCODING, e); + } + } + + /** + * Base64-encode the given byte[] as a string. + * + * @param data the byte array to encode + * @return the encoded string + */ + public static String encodeBase64(final byte[] data) { + return Base64.encodeBase64String(data); + } + + /** + * Base64-encode the given byte[] as a byte[]. + * + * @param data the byte array to encode + * @return the byte[] in base64 + */ + public static byte[] encodeBase64ToByteArray(final byte[] data) { + return Base64.encodeBase64(data); + } + + /** + * Base64 decode operation, which retrieves the equivalent + * byte[] of the data in UTF-8 encoding + * and decodes the result. + * + * @param data the data to encode + * @return the base64 decoded byte[] or null + */ + public static byte[] decodeBase64ToByteArray(final String data) { + try { + final byte[] bytes = data.getBytes(UTF8_ENCODING); + return decodeBase64ToByteArray(bytes); + } catch (final Exception e) { + LOGGER.error("Base64 decoding failed", e); + return null; + } + } + + /** + * Decode the byte[] in base64. + * + * @param data the data to encode + * @return the base64 decoded byte[] or null + */ + public static byte[] decodeBase64ToByteArray(final byte[] data) { + try { + return Base64.decodeBase64(data); + } catch (final Exception e) { + LOGGER.error("Base64 decoding failed", e); + return null; + } + } + + /** + * Decode the byte[] in base64 to a string. + * + * @param bytes the data to encode + * @return the new string in {@link #UTF8_ENCODING}. + */ + public static String decodeByteArrayToString(final byte[] bytes) { + final ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final byte[] buf = new byte[bytes.length]; + try (final InflaterInputStream iis = new InflaterInputStream(bais)) { + int count = iis.read(buf); + while (count != -1) { + baos.write(buf, 0, count); + count = iis.read(buf); + } + return new String(baos.toByteArray(), Charset.forName(UTF8_ENCODING)); + } catch (final Exception e) { + LOGGER.error("Base64 decoding failed", e); + return null; + } + } + + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/CustomBeanValidationPostProcessor.java b/cas-server-core/src/main/java/org/jasig/cas/util/CustomBeanValidationPostProcessor.java new file mode 100644 index 000000000000..dc7324756bb3 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/CustomBeanValidationPostProcessor.java @@ -0,0 +1,67 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import org.springframework.validation.beanvalidation.BeanValidationPostProcessor; + +import java.lang.annotation.ElementType; + +import javax.validation.Configuration; +import javax.validation.Path; +import javax.validation.Path.Node; +import javax.validation.TraversableResolver; +import javax.validation.Validation; +import javax.validation.Validator; + +/** + * Provides a custom {@link javax.validation.TraversableResolver} that should work in JPA2 environments without the JPA2 + * restrictions (i.e. getters for all properties). + * + * @author Scott Battaglia + * @since 3.4 + * + */ +public final class CustomBeanValidationPostProcessor extends BeanValidationPostProcessor { + + /** + * Instantiates a new custom bean validation post processor. + */ + public CustomBeanValidationPostProcessor() { + final Configuration configuration = Validation.byDefaultProvider().configure(); + configuration.traversableResolver(new TraversableResolver() { + + @Override + public boolean isReachable(final Object traversableObject, final Node traversableProperty, + final Class rootBeanType, + final Path pathToTraversableObject, final ElementType elementType) { + return true; + } + + @Override + public boolean isCascadable(final Object traversableObject, final Node traversableProperty, + final Class rootBeanType, + final Path pathToTraversableObject, final ElementType elementType) { + return true; + } + }); + + final Validator validator = configuration.buildValidatorFactory().getValidator(); + setValidator(validator); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/DefaultCipherExecutor.java b/cas-server-core/src/main/java/org/jasig/cas/util/DefaultCipherExecutor.java new file mode 100644 index 000000000000..2b745e41258f --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/DefaultCipherExecutor.java @@ -0,0 +1,212 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.util; + +import org.apache.commons.lang3.StringUtils; +import org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers; +import org.jose4j.jwe.JsonWebEncryption; +import org.jose4j.jwe.KeyManagementAlgorithmIdentifiers; +import org.jose4j.jwk.JsonWebKey; +import org.jose4j.jws.AlgorithmIdentifiers; +import org.jose4j.jws.JsonWebSignature; +import org.jose4j.keys.AesKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.validation.constraints.NotNull; +import java.security.Key; +import java.util.HashMap; +import java.util.Map; + +/** + * The {@link org.jasig.cas.util.DefaultCipherExecutor} is the default + * implementation of {@link org.jasig.cas.util.CipherExecutor}. It provides + * a facade API to encrypt, sign, and verify values. + * + * @author Misagh Moayyed + * @since 4.1 + */ +public final class DefaultCipherExecutor implements CipherExecutor { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final String contentEncryptionAlgorithmIdentifier; + + private final String signingAlgorithm; + + private final Key secretKeyEncryptionKey; + + private final Key secretKeySigningKey; + + /** + * Instantiates a new cipher. + * + *

Note that in order to customize the encryption algorithms, + * you will need to download and install the JCE Unlimited Strength Jurisdiction + * Policy File into your Java installation.

+ * @param secretKeyEncryption the secret key encryption; must be represented as a octet sequence JSON Web Key (JWK) + * @param secretKeySigning the secret key signing; must be represented as a octet sequence JSON Web Key (JWK) + */ + public DefaultCipherExecutor(final String secretKeyEncryption, + final String secretKeySigning) { + this(secretKeyEncryption, secretKeySigning, + ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256, + AlgorithmIdentifiers.HMAC_SHA512); + } + + /** + * Instantiates a new cipher. + * + * @param secretKeyEncryption the key for encryption + * @param secretKeySigning the key for signing + * @param contentEncryptionAlgorithmIdentifier the content encryption algorithm identifier + * @param signingAlgorithm the signing algorithm + */ + public DefaultCipherExecutor(final String secretKeyEncryption, + final String secretKeySigning, + final String contentEncryptionAlgorithmIdentifier, + final String signingAlgorithm) { + this.secretKeyEncryptionKey = prepareJsonWebTokenKey(secretKeyEncryption); + this.contentEncryptionAlgorithmIdentifier = contentEncryptionAlgorithmIdentifier; + + logger.debug("Initialized cipher encryption sequence via [{}]", + contentEncryptionAlgorithmIdentifier); + + this.signingAlgorithm = signingAlgorithm; + this.secretKeySigningKey = new AesKey(secretKeySigning.getBytes()); + + logger.debug("Initialized cipher signing sequence via [{}]", + signingAlgorithm); + + } + + @Override + public String encode(final String value) { + final String encoded = encryptValue(value); + return signValue(encoded); + } + + @Override + public String decode(final String value) { + final String encoded = verifySignature(value); + if (StringUtils.isNotBlank(encoded)) { + return decryptValue(encoded); + } + return null; + } + + /** + * Prepare json web token key. + * + * @param secret the secret + * @return the key + */ + private Key prepareJsonWebTokenKey(final String secret) { + try { + final Map keys = new HashMap<>(2); + keys.put("kty", "oct"); + keys.put("k", secret); + final JsonWebKey jwk = JsonWebKey.Factory.newJwk(keys); + return jwk.getKey(); + } catch (final Exception e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + + /** + * Encrypt the value based on the seed array whose length was given during init, + * and the key and content encryption ids. + * + * @param value the value + * @return the encoded value + */ + private String encryptValue(@NotNull final String value) { + try { + final JsonWebEncryption jwe = new JsonWebEncryption(); + jwe.setPayload(value); + jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.DIRECT); + jwe.setEncryptionMethodHeaderParameter(this.contentEncryptionAlgorithmIdentifier); + jwe.setKey(this.secretKeyEncryptionKey); + logger.debug("Encrypting via [{}]", this.contentEncryptionAlgorithmIdentifier); + return jwe.getCompactSerialization(); + } catch (final Exception e) { + throw new RuntimeException("Ensure that you have installed JCE Unlimited Strength Jurisdiction Policy Files. " + + e.getMessage(), e); + } + } + + /** + * Decrypt value based on the key created during init. + * + * @param value the value + * @return the decrypted value + */ + private String decryptValue(@NotNull final String value) { + try { + final JsonWebEncryption jwe = new JsonWebEncryption(); + jwe.setKey(this.secretKeyEncryptionKey); + jwe.setCompactSerialization(value); + logger.debug("Decrypting value..."); + return jwe.getPayload(); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Signs value based on the signing algorithm and the key length. + * + * @param value the value + * @return the signed value + */ + private String signValue(@NotNull final String value) { + try { + final JsonWebSignature jws = new JsonWebSignature(); + jws.setPayload(value); + jws.setAlgorithmHeaderValue(this.signingAlgorithm); + jws.setKey(this.secretKeySigningKey); + return jws.getCompactSerialization(); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Verify signature. + * + * @param value the value + * @return the value associated with the signature, which may have to + * be decoded, or null. + */ + private String verifySignature(@NotNull final String value) { + try { + final JsonWebSignature jws = new JsonWebSignature(); + jws.setCompactSerialization(value); + jws.setKey(this.secretKeySigningKey); + final boolean verified = jws.verifySignature(); + if (verified) { + logger.debug("Signature successfully verified. Payload is [{}]", jws.getPayload()); + return jws.getPayload(); + } + return null; + } catch (final Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/DefaultLongNumericGenerator.java b/cas-server-core/src/main/java/org/jasig/cas/util/DefaultLongNumericGenerator.java new file mode 100644 index 000000000000..4747ebebdb0d --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/DefaultLongNumericGenerator.java @@ -0,0 +1,90 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * The default numeric generator for generating long values. Implementation + * allows for wrapping (to restart count) if the maximum is reached. + * + * @author Scott Battaglia + + * @since 3.0.0 + */ +public final class DefaultLongNumericGenerator implements LongNumericGenerator { + + /** The maximum length the string can be. */ + private static final int MAX_STRING_LENGTH = Long.toString(Long.MAX_VALUE) + .length(); + + /** The minimum length the String can be. */ + private static final int MIN_STRING_LENGTH = 1; + + private final AtomicLong count; + + /** + * Instantiates a new default long numeric generator. + */ + public DefaultLongNumericGenerator() { + this(0); + // nothing to do + } + + /** + * Instantiates a new default long numeric generator. + * + * @param initialValue the initial value + */ + public DefaultLongNumericGenerator(final long initialValue) { + this.count = new AtomicLong(initialValue); + } + + public long getNextLong() { + return this.getNextValue(); + } + + public String getNextNumberAsString() { + return Long.toString(this.getNextValue()); + } + + @Override + public int maxLength() { + return DefaultLongNumericGenerator.MAX_STRING_LENGTH; + } + + @Override + public int minLength() { + return DefaultLongNumericGenerator.MIN_STRING_LENGTH; + } + + + /** + * Gets the next value. + * + * @return the next value. If the count has reached {@link Long#MAX_VALUE}, + * then {@link Long#MAX_VALUE} is returned. Otherwise, the next increment. + */ + protected long getNextValue() { + if (this.count.compareAndSet(Long.MAX_VALUE, 0)) { + return Long.MAX_VALUE; + } + return this.count.getAndIncrement(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/DefaultRandomStringGenerator.java b/cas-server-core/src/main/java/org/jasig/cas/util/DefaultRandomStringGenerator.java new file mode 100644 index 000000000000..e389b7830828 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/DefaultRandomStringGenerator.java @@ -0,0 +1,102 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import java.security.SecureRandom; + +/** + * Implementation of the RandomStringGenerator that allows you to define the + * length of the random part. + * + * @author Scott Battaglia + + * @since 3.0.0 + */ +public final class DefaultRandomStringGenerator implements RandomStringGenerator { + + /** The default maximum length. */ + protected static final int DEFAULT_MAX_RANDOM_LENGTH = 35; + + /** The array of printable characters to be used in our random string. */ + private static final char[] PRINTABLE_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345679" + .toCharArray(); + + /** An instance of secure random to ensure randomness is secure. */ + private final SecureRandom randomizer = new SecureRandom(); + + /** The maximum length the random string can be. */ + private final int maximumRandomLength; + + /** + * Instantiates a new default random string generator + * with length set to {@link #DEFAULT_MAX_RANDOM_LENGTH}. + */ + public DefaultRandomStringGenerator() { + this.maximumRandomLength = DEFAULT_MAX_RANDOM_LENGTH; + } + + /** + * Instantiates a new default random string generator. + * + * @param maxRandomLength the max random length + */ + public DefaultRandomStringGenerator(final int maxRandomLength) { + this.maximumRandomLength = maxRandomLength; + } + + public int getMinLength() { + return this.maximumRandomLength; + } + + public int getMaxLength() { + return this.maximumRandomLength; + } + + @Override + public String getNewString() { + final byte[] random = getNewStringAsBytes(); + + return convertBytesToString(random); + } + + @Override + public byte[] getNewStringAsBytes() { + final byte[] random = new byte[this.maximumRandomLength]; + + this.randomizer.nextBytes(random); + + return random; + } + + /** + * Convert bytes to string, taking into account {@link #PRINTABLE_CHARACTERS}. + * + * @param random the random + * @return the string + */ + private String convertBytesToString(final byte[] random) { + final char[] output = new char[random.length]; + for (int i = 0; i < random.length; i++) { + final int index = Math.abs(random[i] % PRINTABLE_CHARACTERS.length); + output[i] = PRINTABLE_CHARACTERS[index]; + } + + return new String(output); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/DefaultUniqueTicketIdGenerator.java b/cas-server-core/src/main/java/org/jasig/cas/util/DefaultUniqueTicketIdGenerator.java new file mode 100644 index 000000000000..653942c1a5c3 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/DefaultUniqueTicketIdGenerator.java @@ -0,0 +1,126 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Default implementation of {@link UniqueTicketIdGenerator}. Implementation + * utilizes a DefaultLongNumericGeneraor and a DefaultRandomStringGenerator to + * construct the ticket id. + *

+ * Tickets are of the form [PREFIX]-[SEQUENCE NUMBER]-[RANDOM STRING]-[SUFFIX] + *

+ * + * @author Scott Battaglia + * @since 3.0.0 + */ +public class DefaultUniqueTicketIdGenerator implements UniqueTicketIdGenerator { + + /** The logger instance. */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** The numeric generator to generate the static part of the id. */ + private final NumericGenerator numericGenerator; + + /** The RandomStringGenerator to generate the secure random part of the id. */ + private final RandomStringGenerator randomStringGenerator; + + /** + * Optional suffix to ensure uniqueness across JVMs by specifying unique + * values. + */ + private final String suffix; + + /** + * Creates an instance of DefaultUniqueTicketIdGenerator with default values + * including a {@link DefaultLongNumericGenerator} with a starting value of + * 1. + */ + public DefaultUniqueTicketIdGenerator() { + this(DefaultRandomStringGenerator.DEFAULT_MAX_RANDOM_LENGTH); + } + + /** + * Creates an instance of DefaultUniqueTicketIdGenerator with a specified + * maximum length for the random portion. + * + * @param maxLength the maximum length of the random string used to generate + * the id. + */ + public DefaultUniqueTicketIdGenerator(final int maxLength) { + this(maxLength, null); + } + + /** + * Creates an instance of DefaultUniqueTicketIdGenerator with a specified + * maximum length for the random portion. + * + * @param numericGenerator the numeric generator + * @param randomStringGenerator the random string generator + * @param suffix the value to append at the end of the unique id to ensure + * uniqueness across JVMs. + * @since 4.1.0 + */ + public DefaultUniqueTicketIdGenerator(final NumericGenerator numericGenerator, + final RandomStringGenerator randomStringGenerator, + final String suffix) { + + this.randomStringGenerator = randomStringGenerator; + this.numericGenerator = numericGenerator; + this.suffix = StringUtils.isNoneBlank(suffix) ? '-' + suffix : null; + } + + /** + * Creates an instance of DefaultUniqueTicketIdGenerator with a specified + * maximum length for the random portion. + * + * @param maxLength the maximum length of the random string used to generate + * the id. + * @param suffix the value to append at the end of the unique id to ensure + * uniqueness across JVMs. + */ + public DefaultUniqueTicketIdGenerator(final int maxLength, final String suffix) { + this(new DefaultLongNumericGenerator(1), new DefaultRandomStringGenerator(maxLength), suffix); + } + + + + @Override + public final String getNewTicketId(final String prefix) { + final String number = this.numericGenerator.getNextNumberAsString(); + final StringBuilder buffer = new StringBuilder(prefix.length() + 2 + + (StringUtils.isNotBlank(this.suffix) ? this.suffix.length() : 0) + this.randomStringGenerator.getMaxLength() + + number.length()); + + buffer.append(prefix); + buffer.append('-'); + buffer.append(number); + buffer.append('-'); + buffer.append(this.randomStringGenerator.getNewString()); + + if (this.suffix != null) { + buffer.append(this.suffix); + } + + return buffer.toString(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/HostNameBasedUniqueTicketIdGenerator.java b/cas-server-core/src/main/java/org/jasig/cas/util/HostNameBasedUniqueTicketIdGenerator.java new file mode 100644 index 000000000000..449c330badd4 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/HostNameBasedUniqueTicketIdGenerator.java @@ -0,0 +1,76 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * An implementation of {@link UniqueTicketIdGenerator} that is able auto-configure + * the suffix based on the underlying host name. + * + *

In order to assist with multi-node deployments, in scenarios where CAS configuration + * and specially cas.properties file is externalized, it would be ideal to simply just have one set + * of configuration files for all nodes, such that there would for instance be one cas.properties file + * for all nodes. This would remove the need to copy/sync config files over across nodes, again in a + * situation where they are externalized. + *

The drawback is that in keeping only one cas.properties file, we'd lose the ability + * to define unique host.name property values for each node as the suffix, which would assist with troubleshooting + * and diagnostics. To provide a remedy, this ticket generator is able to retrieve the host.name value directly from + * the actual node name, rather than relying on the configuration, only if one isn't specified in + * the cas.properties file.

+ * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class HostNameBasedUniqueTicketIdGenerator extends DefaultUniqueTicketIdGenerator { + /** + * Instantiates a new Host name based unique ticket id generator. + * + * @param maxLength the max length + */ + public HostNameBasedUniqueTicketIdGenerator(final int maxLength) { + super(maxLength, determineTicketSuffixByHostName()); + } + + /** + * Appends the first part of the host name to the ticket id, + * so as to moderately provide a relevant unique value mapped to + * the host name AND not auto-leak infrastructure data out into the configuration and logs. + *
    + *
  • If the CAS node name is cas-01.sso.edu then, the suffix + * determined would just be cas-01
  • + *
  • If the CAS node name is cas-01 then, the suffix + * determined would just be cas-01
  • + *
+ * @return the shortened ticket suffix based on the hostname + * @since 4.1.0 + */ + private static String determineTicketSuffixByHostName() { + try { + final String hostName = InetAddress.getLocalHost().getCanonicalHostName(); + final int index = hostName.indexOf('.'); + if (index > 0) { + return hostName.substring(0, index); + } + return hostName; + } catch (final UnknownHostException e) { + throw new RuntimeException("Host name could not be determined automatically for the ticket suffix.", e); + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/ISOStandardDateFormat.java b/cas-server-core/src/main/java/org/jasig/cas/util/ISOStandardDateFormat.java new file mode 100644 index 000000000000..22d6831e36b3 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/ISOStandardDateFormat.java @@ -0,0 +1,56 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import org.apache.commons.lang3.time.FastDateFormat; + +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + * A fast date format based on the ISO-8601 standard. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class ISOStandardDateFormat extends FastDateFormat { + + private static final long serialVersionUID = 9196017562782775535L; + + /** The ISO date format used by this formatter. */ + private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + + /** + * Instantiates a new ISO standard date format + * based on the format {@link #DATE_FORMAT}. + */ + public ISOStandardDateFormat() { + super(DATE_FORMAT, TimeZone.getDefault(), Locale.getDefault()); + } + + /** + * Gets the current date and time + * formatted by the pattern specified. + * + * @return the current date and time + */ + public String getCurrentDateAndTime() { + return format(new Date()); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/JsonSerializer.java b/cas-server-core/src/main/java/org/jasig/cas/util/JsonSerializer.java new file mode 100644 index 000000000000..ff1c047fd674 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/JsonSerializer.java @@ -0,0 +1,83 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Serializable; +import java.io.Writer; + +/** + * Interface to define operations needed to map objects from/to JSON clobs. + * @author Misagh Moayyed + * @param the type parameter + * @since 4.1.0 + */ +public interface JsonSerializer extends Serializable { + /** + * Create the object type from the given JSON string. + * @param json the json string + * @return the object instance constructed from JSON + */ + T fromJson(String json); + + /** + * Create the object type from the given JSON reader. + * @param json the json string + * @return the object instance constructed from JSON + */ + T fromJson(Reader json); + + /** + * Create the object type from the given JSON stream. + * @param json the json string + * @return the object instance constructed from JSON + */ + T fromJson(InputStream json); + + /** + * Create the object type from the given JSON file. + * @param json the json string + * @return the object instance constructed from JSON + */ + T fromJson(File json); + + /** + * Serialize the given object to its JSON equivalent to the output stream. + * @param out the output stream + * @param object the object to serialize + */ + void toJson(OutputStream out, T object); + + /** + * Serialize the given object to its JSON equivalent to the output writer. + * @param out the output writer + * @param object the object to serialize + */ + void toJson(Writer out, T object); + + /** + * Serialize the given object to its JSON equivalent to the output file. + * @param out the output file + * @param object the object to serialize + */ + void toJson(File out, T object); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/LockedOutputStream.java b/cas-server-core/src/main/java/org/jasig/cas/util/LockedOutputStream.java new file mode 100644 index 000000000000..7f639353e31c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/LockedOutputStream.java @@ -0,0 +1,68 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileLock; + +/** + * Buffered output stream around a file that is exclusively locked for the + * lifetime of the stream. + * @author Misagh Moayyed + * @author Marvin S. Addison + * @since 4.1.0 + */ +public final class LockedOutputStream extends BufferedOutputStream { + + /** Lock held on file underneath stream. */ + private final FileLock lock; + + /** Flag to indicate underlying stream is already closed. */ + private boolean closed; + + /** + * Creates a new instance by obtaining a lock on the underlying stream + * that is held until the stream is closed. + * + * @param out Output stream. + * @throws IOException If a lock cannot be obtained on the file. + */ + public LockedOutputStream(final FileOutputStream out) throws IOException { + super(out); + this.lock = out.getChannel().lock(); + } + + /** + * {@inheritDoc} + */ + @Override + public void close() throws IOException { + if (closed) { + return; + } + try { + lock.release(); + } finally { + closed = true; + super.close(); + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/LongNumericGenerator.java b/cas-server-core/src/main/java/org/jasig/cas/util/LongNumericGenerator.java new file mode 100644 index 000000000000..66eed1c638c5 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/LongNumericGenerator.java @@ -0,0 +1,36 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +/** + * Interface to guaranteed to return a long. + * + * @author Scott Battaglia + + * @since 3.0.0 + */ +public interface LongNumericGenerator extends NumericGenerator { + + /** + * Get the next long in the sequence. + * + * @return the next long in the sequence. + */ + long getNextLong(); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/NoOpCipherExecutor.java b/cas-server-core/src/main/java/org/jasig/cas/util/NoOpCipherExecutor.java new file mode 100644 index 000000000000..c1959271b153 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/NoOpCipherExecutor.java @@ -0,0 +1,53 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * No-Op cipher executor that does nothing for encryption/decryption. + * @author Misagh Moayyed + * @since 4.1 + */ +public final class NoOpCipherExecutor implements CipherExecutor { + + private static final Logger LOGGER = LoggerFactory.getLogger(NoOpCipherExecutor.class); + + /** + * Instantiates a new No op cipher executor. + * Issues a warning on safety. + */ + public NoOpCipherExecutor() { + LOGGER.warn("[{}] does no encryption and may NOT be safe in a production environment. " + + "Consider using other choices, such as [{}] that handle encryption, signing and verification of " + + "all appropriate values.", this.getClass().getName(), DefaultCipherExecutor.class.getName()); + } + + @Override + public String encode(final String value) { + return value; + } + + @Override + public String decode(final String value) { + return value; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/NumericGenerator.java b/cas-server-core/src/main/java/org/jasig/cas/util/NumericGenerator.java new file mode 100644 index 000000000000..47d26d232e9a --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/NumericGenerator.java @@ -0,0 +1,50 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +/** + * Interface to return a new sequential number for each call. + * + * @author Scott Battaglia + + * @since 3.0.0 + */ +public interface NumericGenerator { + + /** + * Method to retrieve the next number as a String. + * + * @return the String representation of the next number in the sequence + */ + String getNextNumberAsString(); + + /** + * The guaranteed maximum length of a String returned by this generator. + * + * @return the maximum length + */ + int maxLength(); + + /** + * The guaranteed minimum length of a String returned by this generator. + * + * @return the minimum length. + */ + int minLength(); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/Pair.java b/cas-server-core/src/main/java/org/jasig/cas/util/Pair.java new file mode 100644 index 000000000000..b2b16d30fcb9 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/Pair.java @@ -0,0 +1,63 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +/** + * This class contains a pair of objects. + * + * @author Jerome Leleu + * @param the generic type, first item + * @param the generic type, second item + * @since 4.0.0 + */ +public class Pair { + + /** The first object of the pair. */ + private final A first; + + /** The second object of the pair. */ + private final B second; + + /** + * Build a pair. + * + * @param first the first object of the pair. + * @param second the second object of the pair. + */ + public Pair(final A first, final B second) { + this.first = first; + this.second = second; + } + + /** + * Return the first object of the pair. + * @return the first object of the pair. + */ + public final A getFirst() { + return this.first; + } + + /** + * Return the second object of the pair. + * @return the second object of the pair. + */ + public final B getSecond() { + return this.second; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/PrivateKeyFactoryBean.java b/cas-server-core/src/main/java/org/jasig/cas/util/PrivateKeyFactoryBean.java new file mode 100644 index 000000000000..ac3aab7d02e7 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/PrivateKeyFactoryBean.java @@ -0,0 +1,68 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import java.io.InputStream; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; + +import org.springframework.beans.factory.config.AbstractFactoryBean; +import org.springframework.core.io.Resource; + +import javax.validation.constraints.NotNull; + +/** + * Factory Bean for creating a private key from a file. + * + * @author Scott Battaglia + * @since 3.1 + * + */ +public final class PrivateKeyFactoryBean extends AbstractFactoryBean { + + @NotNull + private Resource location; + + @NotNull + private String algorithm; + + @Override + protected PrivateKey createInstance() throws Exception { + try (final InputStream privKey = this.location.getInputStream()) { + final byte[] bytes = new byte[privKey.available()]; + privKey.read(bytes); + final PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(bytes); + final KeyFactory factory = KeyFactory.getInstance(this.algorithm); + return factory.generatePrivate(privSpec); + } + } + + public Class getObjectType() { + return PrivateKey.class; + } + + public void setLocation(final Resource location) { + this.location = location; + } + + public void setAlgorithm(final String algorithm) { + this.algorithm = algorithm; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/PublicKeyFactoryBean.java b/cas-server-core/src/main/java/org/jasig/cas/util/PublicKeyFactoryBean.java new file mode 100644 index 000000000000..7dc4fa17abfb --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/PublicKeyFactoryBean.java @@ -0,0 +1,92 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import java.io.InputStream; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.spec.X509EncodedKeySpec; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.AbstractFactoryBean; +import org.springframework.core.io.Resource; + +import javax.validation.constraints.NotNull; + +/** + * FactoryBean for creating a public key from a file. + * + * @author Scott Battaglia + * @author Misagh Moayyed + * @since 3.1 + */ +public class PublicKeyFactoryBean extends AbstractFactoryBean { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @NotNull + private Resource resource; + + @NotNull + private String algorithm; + + @Override + protected final PublicKey createInstance() throws Exception { + logger.debug("Creating public key instance from [{}] using [{}]", + this.resource.getFilename(), this.algorithm); + + try (final InputStream pubKey = this.resource.getInputStream()) { + final byte[] bytes = new byte[pubKey.available()]; + pubKey.read(bytes); + final X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(bytes); + final KeyFactory factory = KeyFactory.getInstance(this.algorithm); + return factory.generatePublic(pubSpec); + } + } + + public Class getObjectType() { + return PublicKey.class; + } + + public Resource getResource() { + return resource; + } + + public String getAlgorithm() { + return algorithm; + } + + public void setLocation(final Resource resource) { + this.resource = resource; + } + + public void setAlgorithm(final String algorithm) { + this.algorithm = algorithm; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .appendSuper(super.toString()) + .append("resource", this.resource) + .append("algorithm", this.algorithm) + .toString(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/RandomStringGenerator.java b/cas-server-core/src/main/java/org/jasig/cas/util/RandomStringGenerator.java new file mode 100644 index 000000000000..a8d7b93943e4 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/RandomStringGenerator.java @@ -0,0 +1,51 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +/** + * Interface to return a random String. + * + * @author Scott Battaglia + + * @since 3.0.0 + */ +public interface RandomStringGenerator { + + /** + * @return the minimum length as an int guaranteed by this generator. + */ + int getMinLength(); + + /** + * @return the maximum length as an int guaranteed by this generator. + */ + int getMaxLength(); + + /** + * @return the new random string + */ + String getNewString(); + + /** + * Gets the new string as bytes. + * + * @return the new random string as bytes + */ + byte[] getNewStringAsBytes(); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/SpringAwareMessageMessageInterpolator.java b/cas-server-core/src/main/java/org/jasig/cas/util/SpringAwareMessageMessageInterpolator.java new file mode 100644 index 000000000000..b4cd36ec06c2 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/SpringAwareMessageMessageInterpolator.java @@ -0,0 +1,64 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import org.springframework.context.NoSuchMessageException; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceAware; + +import javax.validation.MessageInterpolator; +import javax.validation.Validation; + +import java.util.Locale; + +/** + * Configures the {@link javax.validation.Validator} to check the Spring Messages. + * + * @author Scott Battaglia + + * @since 3.4 + */ +public final class SpringAwareMessageMessageInterpolator implements MessageInterpolator, MessageSourceAware { + + private final MessageInterpolator defaultMessageInterpolator = + Validation.byDefaultProvider().configure().getDefaultMessageInterpolator(); + + private MessageSource messageSource; + + public void setMessageSource(final MessageSource messageSource) { + this.messageSource = messageSource; + } + + @Override + public String interpolate(final String s, final Context context) { + return interpolate(s, context, LocaleContextHolder.getLocale()); + } + + @Override + public String interpolate(final String s, final Context context, final Locale locale) { + try { + return this.messageSource.getMessage(s, + context.getConstraintDescriptor().getAttributes().values().toArray( + new Object[context.getConstraintDescriptor().getAttributes().size()]), locale); + } catch (final NoSuchMessageException e) { + return this.defaultMessageInterpolator.interpolate(s, context, locale); + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/UniqueTicketIdGenerator.java b/cas-server-core/src/main/java/org/jasig/cas/util/UniqueTicketIdGenerator.java new file mode 100644 index 000000000000..c96bdaa4fab4 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/UniqueTicketIdGenerator.java @@ -0,0 +1,35 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +/** + * Interface that enables for pluggable unique ticket Ids strategies. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public interface UniqueTicketIdGenerator { + /** + * Return a new unique ticket id beginning with the prefix. + * + * @param prefix The prefix we want attached to the ticket. + * @return the unique ticket id + */ + String getNewTicketId(String prefix); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/http/HttpClient.java b/cas-server-core/src/main/java/org/jasig/cas/util/http/HttpClient.java new file mode 100644 index 000000000000..2a7799837269 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/http/HttpClient.java @@ -0,0 +1,60 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util.http; + +import javax.validation.constraints.NotNull; +import java.net.URL; + +/** + * Define the behaviour of a HTTP client. + * + * @author Jerome Leleu + * @author Misagh Moayyed + * @since 4.0.0 + */ +public interface HttpClient { + + /** + * Sends a message to a particular endpoint. Option of sending it without + * waiting to ensure a response was returned. + *

+ * This is useful when it doesn't matter about the response as you'll perform no action based on the response. + * + * @param message The message that should be sent to the http endpoint + * @return boolean if the message was sent, or async was used. false if the message failed. + * @since 4.1.0 + */ + boolean sendMessageToEndPoint(@NotNull HttpMessage message); + + /** + * Make a synchronous HTTP(S) call to ensure that the url is reachable. + * + * @param url the url to call + * @return whether the url is valid + */ + boolean isValidEndPoint(@NotNull String url); + + /** + * Make a synchronous HTTP(S) call to ensure that the url is reachable. + * + * @param url the url to call + * @return whether the url is valid + */ + boolean isValidEndPoint(@NotNull URL url); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/http/HttpMessage.java b/cas-server-core/src/main/java/org/jasig/cas/util/http/HttpMessage.java new file mode 100644 index 000000000000..4e87bf7ab2ab --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/http/HttpMessage.java @@ -0,0 +1,123 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util.http; + +import java.net.URL; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +/** + * Abstraction for a message that is sent to an http endpoint. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class HttpMessage { + private static final Logger LOGGER = LoggerFactory.getLogger(HttpMessage.class); + + /** The default asynchronous callbacks enabled. */ + private static final boolean DEFAULT_ASYNCHRONOUS_CALLBACKS_ENABLED = true; + + private final URL url; + private final String message; + + /** + * Whether this message should be sent in an asynchronous fashion. + * Default is true. + **/ + private final boolean asynchronous; + + /** + * The content type for this message once submitted. + * Default is {@link MediaType#APPLICATION_FORM_URLENCODED}. + **/ + private String contentType = MediaType.APPLICATION_FORM_URLENCODED_VALUE; + + /** + * Prepare the sender with a given url and the message to send. + * + * @param url the url to which the message will be sent. + * @param message the message itself. + */ + public HttpMessage(final URL url, final String message) { + this(url, message, DEFAULT_ASYNCHRONOUS_CALLBACKS_ENABLED); + } + + /** + * Prepare the sender with a given url and the message to send. + * + * @param url the url to which the message will be sent. + * @param message the message itself. + * @param async whether the message should be sent asynchronously. + */ + public HttpMessage(final URL url, final String message, final boolean async) { + this.url = url; + this.message = message; + this.asynchronous = async; + } + + protected boolean isAsynchronous() { + return this.asynchronous; + } + + protected final URL getUrl() { + return this.url; + } + + protected final String getMessage() { + return this.formatOutputMessageInternal(this.message); + } + + protected final String getContentType() { + return this.contentType; + } + + protected final void setContentType(final String type) { + this.contentType = type; + } + + /** + * Encodes the message in UTF-8 format in preparation to send. + * @param message Message to format and encode + * @return The encoded message. + */ + protected String formatOutputMessageInternal(final String message) { + try { + return URLEncoder.encode(message, "UTF-8"); + } catch (final UnsupportedEncodingException e) { + LOGGER.warn(e.getMessage(), e); + } + return message; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("url", this.url) + .append("message", this.message) + .append("asynchronous", this.asynchronous) + .append("contentType", this.contentType) + .toString(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/http/SimpleHttpClient.java b/cas-server-core/src/main/java/org/jasig/cas/util/http/SimpleHttpClient.java new file mode 100644 index 000000000000..d03bc852615b --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/http/SimpleHttpClient.java @@ -0,0 +1,250 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util.http; + +import com.google.common.collect.ImmutableList; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.BasicResponseHandler; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.FutureRequestExecutionService; +import org.apache.http.impl.client.HttpRequestFutureTask; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.util.Assert; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSocketFactory; +import javax.validation.constraints.NotNull; +import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; + +/** + * Implementationn of CAS {@link HttpClient} + * which delegates requests to a {@link #httpClient} instance. + * + * @author Jerome Leleu + * @author Scott Battaglia + * @author Misagh Moayyed + * @since 3.1 + */ +final class SimpleHttpClient implements HttpClient, Serializable, DisposableBean { + + /** Unique Id for serialization. */ + private static final long serialVersionUID = -4949380008568071855L; + + private static final Logger LOGGER = LoggerFactory.getLogger(SimpleHttpClient.class); + + /** the acceptable codes supported by this client. */ + private final List acceptableCodes; + + /** the HTTP client for this client. */ + private final CloseableHttpClient httpClient; + + /** the request executor service for this client. */ + private final FutureRequestExecutionService requestExecutorService; + + /** + * Instantiates a new Simple HTTP client, based on the provided inputs. + * + * @param acceptableCodes the acceptable codes of the client + * @param httpClient the HTTP client used by the client + * @param requestExecutorService the request executor service used by the client + */ + public SimpleHttpClient(final List acceptableCodes, final CloseableHttpClient httpClient, + final FutureRequestExecutionService requestExecutorService) { + this.acceptableCodes = ImmutableList.copyOf(acceptableCodes); + this.httpClient = httpClient; + this.requestExecutorService = requestExecutorService; + } + + @Override + public boolean sendMessageToEndPoint(final HttpMessage message) { + Assert.notNull(this.httpClient); + + try { + final HttpPost request = new HttpPost(message.getUrl().toURI()); + request.addHeader("Content-Type", message.getContentType()); + + final StringEntity entity = new StringEntity(message.getMessage(), ContentType.create(message.getContentType())); + request.setEntity(entity); + + final HttpRequestFutureTask task = this.requestExecutorService.execute(request, + HttpClientContext.create(), new BasicResponseHandler()); + + if (message.isAsynchronous()) { + return true; + } + + return StringUtils.isNotBlank(task.get()); + } catch (final RejectedExecutionException e) { + LOGGER.warn(e.getMessage(), e); + return false; + } catch (final Exception e) { + LOGGER.trace(e.getMessage(), e); + return false; + } + } + + @Override + public boolean isValidEndPoint(final String url) { + try { + final URL u = new URL(url); + return isValidEndPoint(u); + } catch (final MalformedURLException e) { + LOGGER.error(e.getMessage(), e); + return false; + } + } + + @Override + public boolean isValidEndPoint(final URL url) { + Assert.notNull(this.httpClient); + + HttpEntity entity = null; + + try (final CloseableHttpResponse response = this.httpClient.execute(new HttpGet(url.toURI()))) { + final int responseCode = response.getStatusLine().getStatusCode(); + + for (final int acceptableCode : this.acceptableCodes) { + if (responseCode == acceptableCode) { + LOGGER.debug("Response code from server matched {}.", responseCode); + return true; + } + } + + LOGGER.debug("Response code did not match any of the acceptable response codes. Code returned was {}", + responseCode); + + if (responseCode == HttpStatus.SC_INTERNAL_SERVER_ERROR) { + final String value = response.getStatusLine().getReasonPhrase(); + LOGGER.error("There was an error contacting the endpoint: {}; The error was:\n{}", url.toExternalForm(), + value); + } + + entity = response.getEntity(); + } catch (final Exception e) { + LOGGER.error(e.getMessage(), e); + } finally { + EntityUtils.consumeQuietly(entity); + } + return false; + } + + /** + * Shutdown the executor service and close the http client. + * @throws Exception if the executor cannot properly shut down + */ + public void destroy() throws Exception { + IOUtils.closeQuietly(this.requestExecutorService); + } + + /** + * @deprecated As of 4.1 + * Note that changing this executor will affect all httpClients. While not ideal, this change + * was made because certain ticket registries + * were persisting the HttpClient and thus getting serializable exceptions. + * @param executorService The executor service to send messages to end points. + */ + @Deprecated + public void setExecutorService(@NotNull final ExecutorService executorService) { + LOGGER.warn("setExecutorService() is deprecated and has no effect. Consider using SimpleHttpClientFactoryBean instead."); + } + + /** + * @deprecated As of 4.1 + * Set the acceptable HTTP status codes that we will use to determine if the + * response from the URL was correct. + * + * @param acceptableCodes an array of status code integers. + */ + @Deprecated + public void setAcceptableCodes(final int[] acceptableCodes) { + LOGGER.warn("setAcceptableCodes() is deprecated and has no effect. Consider using SimpleHttpClientFactoryBean instead."); + } + + /** + * @deprecated As of 4.1 + * Sets a specified timeout value, in milliseconds, to be used when opening the endpoint url. + * @param connectionTimeout specified timeout value in milliseconds + */ + @Deprecated + public void setConnectionTimeout(final int connectionTimeout) { + LOGGER.warn("setConnectionTimeout() is deprecated and has no effect. Consider using SimpleHttpClientFactoryBean instead."); + } + + /** + * @deprecated As of 4.1 + * Sets a specified timeout value, in milliseconds, to be used when reading from the endpoint url. + * @param readTimeout specified timeout value in milliseconds + */ + @Deprecated + public void setReadTimeout(final int readTimeout) { + LOGGER.warn("setReadTimeout() is deprecated and has no effect. Consider using SimpleHttpClientFactoryBean instead."); + } + + /** + * @deprecated As of 4.1 + * Determines the behavior on receiving 3xx responses from HTTP endpoints. + * + * @param follow True to follow 3xx redirects (default), false otherwise. + */ + @Deprecated + public void setFollowRedirects(final boolean follow) { + LOGGER.warn("setFollowRedirects() is deprecated and has no effect. Consider using SimpleHttpClientFactoryBean instead."); + } + + /** + * @deprecated As of 4.1 + * Set the SSL socket factory be used by the URL when submitting + * request to check for URL endpoint validity. + * @param factory ssl socket factory instance to use + * @see #isValidEndPoint(URL) + */ + @Deprecated + public void setSSLSocketFactory(final SSLSocketFactory factory) { + LOGGER.warn("setSSLSocketFactory() is deprecated and has no effect. Consider using SimpleHttpClientFactoryBean instead."); + } + + /** + * @deprecated As of 4.1 + * Set the hostname verifier be used by the URL when submitting + * request to check for URL endpoint validity. + * @param verifier hostname verifier instance to use + * @see #isValidEndPoint(URL) + */ + @Deprecated + public void setHostnameVerifier(final HostnameVerifier verifier) { + LOGGER.warn("setHostnameVerifier() is deprecated and has no effect. Consider using SimpleHttpClientFactoryBean instead."); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/http/SimpleHttpClientFactoryBean.java b/cas-server-core/src/main/java/org/jasig/cas/util/http/SimpleHttpClientFactoryBean.java new file mode 100644 index 000000000000..fc955b6f69a1 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/http/SimpleHttpClientFactoryBean.java @@ -0,0 +1,439 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util.http; + +import com.google.common.collect.ImmutableList; +import com.google.common.primitives.Ints; +import org.apache.http.ConnectionReuseStrategy; +import org.apache.http.Header; +import org.apache.http.HttpHost; +import org.apache.http.client.AuthenticationStrategy; +import org.apache.http.client.ConnectionBackoffStrategy; +import org.apache.http.client.CookieStore; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.RedirectStrategy; +import org.apache.http.client.ServiceUnavailableRetryStrategy; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.LayeredConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.DefaultHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.DefaultConnectionReuseStrategy; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.DefaultBackoffStrategy; +import org.apache.http.impl.client.DefaultRedirectStrategy; +import org.apache.http.impl.client.DefaultServiceUnavailableRetryStrategy; +import org.apache.http.impl.client.FutureRequestExecutionService; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.client.ProxyAuthenticationStrategy; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.FactoryBean; + +import javax.net.ssl.HostnameVerifier; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * The factory to build a {@link SimpleHttpClient}. + * + * @author Jerome Leleu + * @since 4.1.0 + */ +public final class SimpleHttpClientFactoryBean implements FactoryBean { + + /** Max connections per route. */ + public static final int MAX_CONNECTIONS_PER_ROUTE = 50; + + private static final Logger LOGGER = LoggerFactory.getLogger(SimpleHttpClientFactoryBean.class); + + private static final int MAX_POOLED_CONNECTIONS = 100; + + private static final int DEFAULT_THREADS_NUMBER = 200; + + private static final int DEFAULT_TIMEOUT = 5000; + + /** The default status codes we accept. */ + private static final int[] DEFAULT_ACCEPTABLE_CODES = new int[] { + HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_NOT_MODIFIED, + HttpURLConnection.HTTP_MOVED_TEMP, HttpURLConnection.HTTP_MOVED_PERM, + HttpURLConnection.HTTP_ACCEPTED}; + + /** 20% of the total of threads in the pool to handle overhead. */ + private static final int DEFAULT_QUEUE_SIZE = (int) (DEFAULT_THREADS_NUMBER * 0.2); + + /** The number of threads used to build the pool of threads (if no executorService provided). */ + private int threadsNumber = DEFAULT_THREADS_NUMBER; + + /** The queue size to absorb additional tasks when the threads pool is saturated (if no executorService provided). */ + private int queueSize = DEFAULT_QUEUE_SIZE; + + /** The Max pooled connections. */ + private int maxPooledConnections = MAX_POOLED_CONNECTIONS; + + /** The Max connections per each route connections. */ + private int maxConnectionsPerRoute = MAX_CONNECTIONS_PER_ROUTE; + + /** List of HTTP status codes considered valid by the caller. */ + @NotNull + @Size(min = 1) + private List acceptableCodes = Ints.asList(DEFAULT_ACCEPTABLE_CODES); + + @Min(0) + private int connectionTimeout = DEFAULT_TIMEOUT; + + @Min(0) + private int readTimeout = DEFAULT_TIMEOUT; + + private RedirectStrategy redirectionStrategy = new DefaultRedirectStrategy(); + + /** + * The socket factory to be used when verifying the validity of the endpoint. + */ + private SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactory.getSocketFactory(); + + /** + * The hostname verifier to be used when verifying the validity of the endpoint. + */ + private HostnameVerifier hostnameVerifier = new DefaultHostnameVerifier(); + + /** The credentials provider for endpoints that require authentication. */ + private CredentialsProvider credentialsProvider; + + /** The cookie store for authentication. */ + private CookieStore cookieStore; + + /** Interface for deciding whether a connection can be re-used for subsequent requests and should be kept alive. **/ + private ConnectionReuseStrategy connectionReuseStrategy = new DefaultConnectionReuseStrategy(); + + /** + * When managing a dynamic number of connections for a given route, this strategy assesses whether a + * given request execution outcome should result in a backoff + * signal or not, based on either examining the Throwable that resulted or by examining + * the resulting response (e.g. for its status code). + */ + private ConnectionBackoffStrategy connectionBackoffStrategy = new DefaultBackoffStrategy(); + + /** Strategy interface that allows API users to plug in their own logic to control whether or not a retry + * should automatically be done, how many times it should be retried and so on. + */ + private ServiceUnavailableRetryStrategy serviceUnavailableRetryStrategy = new DefaultServiceUnavailableRetryStrategy(); + + /** Default headers to be sent. **/ + private Collection defaultHeaders = Collections.emptyList(); + + /** Default strategy implementation for proxy host authentication.**/ + private AuthenticationStrategy proxyAuthenticationStrategy = new ProxyAuthenticationStrategy(); + + /** Determines whether circular redirects (redirects to the same location) should be allowed. **/ + private boolean circularRedirectsAllowed = true; + + /** Determines whether authentication should be handled automatically. **/ + private boolean authenticationEnabled; + + /** Determines whether redirects should be handled automatically. **/ + private boolean redirectsEnabled = true; + + /** + * The executor service used to create a {@link #buildRequestExecutorService}. + */ + private ExecutorService executorService; + + @Override + public SimpleHttpClient getObject() throws Exception { + + final CloseableHttpClient httpClient = buildHttpClient(); + + final FutureRequestExecutionService requestExecutorService = buildRequestExecutorService(httpClient); + + return new SimpleHttpClient(this.acceptableCodes, httpClient, requestExecutorService); + } + + @Override + public Class getObjectType() { + return SimpleHttpClient.class; + } + + @Override + public boolean isSingleton() { + return false; + } + + /** + * Build a HTTP client based on the current properties. + * + * @return the built HTTP client + */ + private CloseableHttpClient buildHttpClient() { + try { + + final ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory(); + final LayeredConnectionSocketFactory sslsf = this.sslSocketFactory; + + final Registry registry = RegistryBuilder.create() + .register("http", plainsf) + .register("https", sslsf) + .build(); + + final PoolingHttpClientConnectionManager connMgmr = new PoolingHttpClientConnectionManager(registry); + connMgmr.setMaxTotal(this.maxPooledConnections); + connMgmr.setDefaultMaxPerRoute(this.maxConnectionsPerRoute); + + final HttpHost httpHost = new HttpHost(InetAddress.getLocalHost()); + final HttpRoute httpRoute = new HttpRoute(httpHost); + connMgmr.setMaxPerRoute(httpRoute, MAX_CONNECTIONS_PER_ROUTE); + + final RequestConfig requestConfig = RequestConfig.custom() + .setSocketTimeout(this.readTimeout) + .setConnectTimeout(this.connectionTimeout) + .setConnectionRequestTimeout(this.connectionTimeout) + .setStaleConnectionCheckEnabled(true) + .setCircularRedirectsAllowed(this.circularRedirectsAllowed) + .setRedirectsEnabled(this.redirectsEnabled) + .setAuthenticationEnabled(this.authenticationEnabled) + .build(); + + + final HttpClientBuilder builder = HttpClients.custom() + .setConnectionManager(connMgmr) + .setDefaultRequestConfig(requestConfig) + .setSSLSocketFactory(sslsf) + .setSSLHostnameVerifier(this.hostnameVerifier) + .setRedirectStrategy(this.redirectionStrategy) + .setDefaultCredentialsProvider(this.credentialsProvider) + .setDefaultCookieStore(this.cookieStore) + .setConnectionReuseStrategy(this.connectionReuseStrategy) + .setConnectionBackoffStrategy(this.connectionBackoffStrategy) + .setServiceUnavailableRetryStrategy(this.serviceUnavailableRetryStrategy) + .setProxyAuthenticationStrategy(this.proxyAuthenticationStrategy) + .setDefaultHeaders(this.defaultHeaders) + .useSystemProperties(); + + return builder.build(); + + } catch (final Exception e) { + LOGGER.error(e.getMessage(), e); + throw new RuntimeException(e); + } + } + + /** + * Build a {@link FutureRequestExecutionService} from the current properties and a HTTP client. + * + * @param httpClient the provided HTTP client + * @return the built request executor service + */ + private FutureRequestExecutionService buildRequestExecutorService(final CloseableHttpClient httpClient) { + + final ExecutorService definedExecutorService; + // no executor service provided -> create a default one + if (this.executorService == null) { + definedExecutorService = new ThreadPoolExecutor(this.threadsNumber, this.threadsNumber, + 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(this.queueSize)); + } else { + definedExecutorService = this.executorService; + } + + return new FutureRequestExecutionService(httpClient, definedExecutorService); + } + + public ExecutorService getExecutorService() { + return this.executorService; + } + + public void setExecutorService(final ExecutorService executorService) { + this.executorService = executorService; + } + + public int getThreadsNumber() { + return this.threadsNumber; + } + + public void setThreadsNumber(final int threadsNumber) { + this.threadsNumber = threadsNumber; + } + + public int getQueueSize() { + return this.queueSize; + } + + public void setQueueSize(final int queueSize) { + this.queueSize = queueSize; + } + + public int getMaxPooledConnections() { + return this.maxPooledConnections; + } + + public void setMaxPooledConnections(final int maxPooledConnections) { + this.maxPooledConnections = maxPooledConnections; + } + + public int getMaxConnectionsPerRoute() { + return this.maxConnectionsPerRoute; + } + + public void setMaxConnectionsPerRoute(final int maxConnectionsPerRoute) { + this.maxConnectionsPerRoute = maxConnectionsPerRoute; + } + + public List getAcceptableCodes() { + return ImmutableList.copyOf(this.acceptableCodes); + } + + public void setAcceptableCodes(final int[] acceptableCodes) { + this.acceptableCodes = Ints.asList(acceptableCodes); + } + + public int getConnectionTimeout() { + return this.connectionTimeout; + } + + public void setConnectionTimeout(final int connectionTimeout) { + this.connectionTimeout = connectionTimeout; + } + + public int getReadTimeout() { + return this.readTimeout; + } + + public void setReadTimeout(final int readTimeout) { + this.readTimeout = readTimeout; + } + + public RedirectStrategy getRedirectionStrategy() { + return this.redirectionStrategy; + } + + public void setRedirectionStrategy(final RedirectStrategy redirectionStrategy) { + this.redirectionStrategy = redirectionStrategy; + } + + public SSLConnectionSocketFactory getSslSocketFactory() { + return this.sslSocketFactory; + } + + public void setSslSocketFactory(final SSLConnectionSocketFactory sslSocketFactory) { + this.sslSocketFactory = sslSocketFactory; + } + + public HostnameVerifier getHostnameVerifier() { + return this.hostnameVerifier; + } + + public void setHostnameVerifier(final HostnameVerifier hostnameVerifier) { + this.hostnameVerifier = hostnameVerifier; + } + + public CredentialsProvider getCredentialsProvider() { + return this.credentialsProvider; + } + + public void setCredentialsProvider(final CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + } + + public CookieStore getCookieStore() { + return this.cookieStore; + } + + public void setCookieStore(final CookieStore cookieStore) { + this.cookieStore = cookieStore; + } + + public ConnectionReuseStrategy getConnectionReuseStrategy() { + return this.connectionReuseStrategy; + } + + public void setConnectionReuseStrategy(final ConnectionReuseStrategy connectionReuseStrategy) { + this.connectionReuseStrategy = connectionReuseStrategy; + } + + public ConnectionBackoffStrategy getConnectionBackoffStrategy() { + return this.connectionBackoffStrategy; + } + + public void setConnectionBackoffStrategy(final ConnectionBackoffStrategy connectionBackoffStrategy) { + this.connectionBackoffStrategy = connectionBackoffStrategy; + } + + public ServiceUnavailableRetryStrategy getServiceUnavailableRetryStrategy() { + return this.serviceUnavailableRetryStrategy; + } + + public void setServiceUnavailableRetryStrategy(final ServiceUnavailableRetryStrategy serviceUnavailableRetryStrategy) { + this.serviceUnavailableRetryStrategy = serviceUnavailableRetryStrategy; + } + + public Collection getDefaultHeaders() { + return this.defaultHeaders; + } + + public void setDefaultHeaders(final Collection defaultHeaders) { + this.defaultHeaders = defaultHeaders; + } + + public AuthenticationStrategy getProxyAuthenticationStrategy() { + return this.proxyAuthenticationStrategy; + } + + public void setProxyAuthenticationStrategy(final AuthenticationStrategy proxyAuthenticationStrategy) { + this.proxyAuthenticationStrategy = proxyAuthenticationStrategy; + } + + public boolean isCircularRedirectsAllowed() { + return this.circularRedirectsAllowed; + } + + public void setCircularRedirectsAllowed(final boolean circularRedirectsAllowed) { + this.circularRedirectsAllowed = circularRedirectsAllowed; + } + + public boolean isAuthenticationEnabled() { + return this.authenticationEnabled; + } + + public void setAuthenticationEnabled(final boolean authenticationEnabled) { + this.authenticationEnabled = authenticationEnabled; + } + + public boolean isRedirectsEnabled() { + return this.redirectsEnabled; + } + + public void setRedirectsEnabled(final boolean redirectsEnabled) { + this.redirectsEnabled = redirectsEnabled; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/package.html b/cas-server-core/src/main/java/org/jasig/cas/util/package.html new file mode 100644 index 000000000000..5a0ca130c031 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/package.html @@ -0,0 +1,27 @@ + + + + +Various utility classes to generate unique ids and work with urls. + + + diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/services/DefaultRegisteredServiceCipherExecutor.java b/cas-server-core/src/main/java/org/jasig/cas/util/services/DefaultRegisteredServiceCipherExecutor.java new file mode 100644 index 000000000000..7e279cb752be --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/services/DefaultRegisteredServiceCipherExecutor.java @@ -0,0 +1,134 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.util.services; + +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.util.CompressionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.Cipher; +import java.security.PublicKey; + +/** + * Default cipher implementation based on public keys. + * + * @author Misagh Moayyed + * @since 4.1 + */ +public class DefaultRegisteredServiceCipherExecutor implements RegisteredServiceCipherExecutor { + private static final String UTF8_ENCODING = "UTF-8"; + + /** Logger instance. **/ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * Encrypt using the given cipher associated with the service, + * and encode the data in base 64. + * + * @param data the data + * @return the encoded piece of data in base64 + */ + @Override + public final String encode(final String data, final RegisteredService service) { + try { + final PublicKey publicKey = createRegisteredServicePublicKey(service); + final byte[] result = encodeInternal(data, publicKey, service); + if (result != null) { + return CompressionUtils.encodeBase64(result); + } + } catch (final Exception e) { + logger.warn(e.getMessage(), e); + } + + return null; + } + + /** + * Encode internally, meant to be called by extensions. + * Default behavior will encode the data based on the + * registered service public key's algorithm using {@link javax.crypto.Cipher}. + * + * @param data the data + * @param publicKey the public key + * @param registeredService the registered service + * @return a byte[] that contains the encrypted result + */ + protected byte[] encodeInternal(final String data, final PublicKey publicKey, + final RegisteredService registeredService) { + try { + final Cipher cipher = initializeCipherBasedOnServicePublicKey(publicKey, registeredService); + if (cipher != null) { + return cipher.doFinal(data.getBytes(UTF8_ENCODING)); + } + } catch (final Exception e) { + throw new RuntimeException(e); + } + return null; + } + + /** + * Create registered service public key defined. + * + * @param registeredService the registered service + * @return the public key + * @throws Exception the exception, if key cant be created + */ + private PublicKey createRegisteredServicePublicKey(final RegisteredService registeredService) throws Exception { + if (registeredService.getPublicKey() == null) { + logger.debug("No public key is defined for service [{}]. No encoding will take place.", + registeredService); + return null; + } + final PublicKey publicKey = registeredService.getPublicKey().createInstance(); + if (publicKey == null) { + logger.debug("No public key instance created for service [{}]. No encoding will take place.", + registeredService); + return null; + } + return publicKey; + } + + /** + * Initialize cipher based on service public key. + * + * @param publicKey the public key + * @param registeredService the registered service + * @return the false if no public key is found + * or if cipher cannot be initialized, etc. + */ + private Cipher initializeCipherBasedOnServicePublicKey(final PublicKey publicKey, + final RegisteredService registeredService) { + try { + logger.debug("Using public key [{}] to initialize the cipher", + registeredService.getPublicKey()); + + final Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm()); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + logger.debug("Initialized cipher in encrypt-mode via the public key algorithm [{}]", + publicKey.getAlgorithm()); + return cipher; + } catch (final Exception e) { + logger.warn("Cipher could not be initialized for service [{}]. Error [{}]", + registeredService, e.getMessage()); + } + return null; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/services/RegisteredServiceJsonSerializer.java b/cas-server-core/src/main/java/org/jasig/cas/util/services/RegisteredServiceJsonSerializer.java new file mode 100644 index 000000000000..e18c8e83a17e --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/services/RegisteredServiceJsonSerializer.java @@ -0,0 +1,101 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util.services; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.RegisteredServiceAccessStrategy; +import org.jasig.cas.services.RegisteredServiceProxyPolicy; +import org.jasig.cas.util.AbstractJacksonBackedJsonSerializer; + +import java.net.URL; +import java.util.Map; + +/** + * Serializes registered services to JSON based on the Jackson JSON library. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class RegisteredServiceJsonSerializer extends AbstractJacksonBackedJsonSerializer { + private static final long serialVersionUID = 7645698151115635245L; + + /** + * Mixins are added to the object mapper in order to + * ignore certain method signatures from serialization + * that are otherwise treated as getters. Each mixin + * implements the appropriate interface as a private + * dummy class and is annotated with JsonIgnore elements + * throughout. This helps us catch errors at compile-time + * when the interface changes. + * @return the prepped object mapper. + */ + @Override + protected ObjectMapper initializeObjectMapper() { + final ObjectMapper mapper = super.initializeObjectMapper(); + mapper.addMixInAnnotations(RegisteredServiceProxyPolicy.class, RegisteredServiceProxyPolicyMixin.class); + mapper.addMixInAnnotations(RegisteredServiceAccessStrategy.class, RegisteredServiceAuthorizationStrategyMixin.class); + return mapper; + } + + private static class RegisteredServiceProxyPolicyMixin implements RegisteredServiceProxyPolicy { + + private static final long serialVersionUID = 4854597398304437341L; + + @JsonIgnore + @Override + public boolean isAllowedToProxy() { + return false; + }; + + @JsonIgnore + @Override + public boolean isAllowedProxyCallbackUrl(final URL pgtUrl) { + return false; + }; + } + + private static class RegisteredServiceAuthorizationStrategyMixin implements RegisteredServiceAccessStrategy { + + private static final long serialVersionUID = -5070823601540670379L; + + @JsonIgnore + @Override + public boolean isServiceAccessAllowed() { + return false; + }; + + @JsonIgnore + @Override + public boolean isServiceAccessAllowedForSso() { + return false; + } + + @JsonIgnore + @Override + public boolean doPrincipalAttributesAllowServiceAccess(final Map principalAttributes) { + return false; + }; + } + + @Override + protected Class getTypeToSerialize() { + return RegisteredService.class; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/validation/AbstractCasProtocolValidationSpecification.java b/cas-server-core/src/main/java/org/jasig/cas/validation/AbstractCasProtocolValidationSpecification.java new file mode 100644 index 000000000000..6cc44ae70717 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/validation/AbstractCasProtocolValidationSpecification.java @@ -0,0 +1,87 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.validation; + +/** + * Base validation specification for the CAS protocol. This specification checks + * for the presence of renew=true and if requested, succeeds only if ticket + * validation is occurring from a new login. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public abstract class AbstractCasProtocolValidationSpecification implements ValidationSpecification { + + /** The default value for the renew attribute is false. */ + private static final boolean DEFAULT_RENEW = false; + + /** Denotes whether we should always authenticate or not. */ + private boolean renew; + + /** + * Instantiates a new abstract cas protocol validation specification. + */ + public AbstractCasProtocolValidationSpecification() { + this.renew = DEFAULT_RENEW; + } + + /** + * Instantiates a new abstract cas protocol validation specification. + * + * @param renew the renew + */ + public AbstractCasProtocolValidationSpecification(final boolean renew) { + this.renew = renew; + } + + /** + * Method to set the renew requirement. + * + * @param renew The renew value we want. + */ + public final void setRenew(final boolean renew) { + this.renew = renew; + } + + /** + * Method to determine if we require renew to be true. + * + * @return true if renew is required, false otherwise. + */ + public final boolean isRenew() { + return this.renew; + } + + /* (non-Javadoc) + * @see org.jasig.cas.validation.ValidationSpecification#isSatisfiedBy(org.jasig.cas.validation.Assertion) + */ + @Override + public final boolean isSatisfiedBy(final Assertion assertion) { + return isSatisfiedByInternal(assertion) + && (!this.renew || assertion.isFromNewLogin()); + } + + /** + * Template method to allow for additional checks by subclassed methods + * without needing to call super.isSatisfiedBy(...). + * @param assertion the assertion + * @return true, if the subclass implementation is satisfied by the assertion + */ + protected abstract boolean isSatisfiedByInternal(final Assertion assertion); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/validation/Cas10ProtocolValidationSpecification.java b/cas-server-core/src/main/java/org/jasig/cas/validation/Cas10ProtocolValidationSpecification.java new file mode 100644 index 000000000000..2082f8a9ba3a --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/validation/Cas10ProtocolValidationSpecification.java @@ -0,0 +1,53 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.validation; + +/** + * Validation specification for the CAS 1.0 protocol. This specification checks + * for the presence of renew=true and if requested, succeeds only if ticket + * validation is occurring from a new login. Additionally, validation will fail + * if passed a proxy ticket. + * + * @author Scott Battaglia + * @author Drew Mazurek + * @since 3.0.0 + */ +public final class Cas10ProtocolValidationSpecification extends AbstractCasProtocolValidationSpecification { + + /** + * Instantiates a new cas10 protocol validation specification. + */ + public Cas10ProtocolValidationSpecification() { + super(); + } + + /** + * Instantiates a new cas10 protocol validation specification. + * + * @param renew the renew + */ + public Cas10ProtocolValidationSpecification(final boolean renew) { + super(renew); + } + + @Override + protected boolean isSatisfiedByInternal(final Assertion assertion) { + return (assertion.getChainedAuthentications().size() == 1); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/validation/Cas20ProtocolValidationSpecification.java b/cas-server-core/src/main/java/org/jasig/cas/validation/Cas20ProtocolValidationSpecification.java new file mode 100644 index 000000000000..4456bc72fc22 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/validation/Cas20ProtocolValidationSpecification.java @@ -0,0 +1,52 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.validation; + +/** + * Validation specification for the CAS 2.0 protocol. This specification extends + * the Cas10ProtocolValidationSpecification, checking for the presence of + * renew=true and if requested, succeeding only if ticket validation is + * occurring from a new login. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public class Cas20ProtocolValidationSpecification extends AbstractCasProtocolValidationSpecification { + + /** + * Instantiates a new cas20 protocol validation specification. + */ + public Cas20ProtocolValidationSpecification() { + super(); + } + + /** + * Instantiates a new cas20 protocol validation specification. + * + * @param renew the renew + */ + public Cas20ProtocolValidationSpecification(final boolean renew) { + super(renew); + } + + @Override + protected boolean isSatisfiedByInternal(final Assertion assertion) { + return true; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/validation/Cas20WithoutProxyingValidationSpecification.java b/cas-server-core/src/main/java/org/jasig/cas/validation/Cas20WithoutProxyingValidationSpecification.java new file mode 100644 index 000000000000..95f68357c3e8 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/validation/Cas20WithoutProxyingValidationSpecification.java @@ -0,0 +1,53 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.validation; + +/** + * Validation specification for the CAS 2.0 protocol. This specification extends + * the Cas20ProtocolValidationSpecification, checking for the presence of + * renew=true and if requested, succeeding only if ticket validation is + * occurring from a new login. Additionally, this specification will not accept + * proxied authentications. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public class Cas20WithoutProxyingValidationSpecification extends AbstractCasProtocolValidationSpecification { + + /** + * Instantiates a new cas20 without proxying validation specification. + */ + public Cas20WithoutProxyingValidationSpecification() { + super(); + } + + /** + * Instantiates a new cas20 without proxying validation specification. + * + * @param renew the renew + */ + public Cas20WithoutProxyingValidationSpecification(final boolean renew) { + super(renew); + } + + @Override + protected boolean isSatisfiedByInternal(final Assertion assertion) { + return (assertion.getChainedAuthentications().size() == 1); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/validation/ImmutableAssertion.java b/cas-server-core/src/main/java/org/jasig/cas/validation/ImmutableAssertion.java new file mode 100644 index 000000000000..fbfd358e1705 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/validation/ImmutableAssertion.java @@ -0,0 +1,125 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.validation; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; +import org.springframework.util.Assert; + +/** + * An immutable, serializable ticket validation assertion. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * + * @since 3.0.0 + */ +public final class ImmutableAssertion implements Assertion, Serializable { + + /** Unique Id for Serialization. */ + private static final long serialVersionUID = -3348826049921010423L; + + /** Primary authentication. */ + private final Authentication primaryAuthentication; + + /** Chained authentications. */ + private final List chainedAuthentications; + + /** Was this the result of a new login. */ + private final boolean fromNewLogin; + + /** The service we are asserting this ticket for. */ + private final Service service; + + /** + * Creates a new instance with required parameters. + * + * @param primary Primary authentication. + * @param chained Chained authentitications. + * @param service The service we are asserting this ticket for. + * @param fromNewLogin True if the ticket was issued as a result of authentication, false otherwise. + * + * @throws IllegalArgumentException If any of the given arguments do not meet requirements. + */ + public ImmutableAssertion( + final Authentication primary, + final List chained, + final Service service, + final boolean fromNewLogin) { + + Assert.notNull(primary, "primary authentication cannot be null"); + Assert.notNull(chained, "chained authentications cannot be null"); + Assert.notNull(service, "service cannot be null"); + Assert.notEmpty(chained, "chained authentications cannot be empty"); + + this.primaryAuthentication = primary; + this.chainedAuthentications = chained; + this.service = service; + this.fromNewLogin = fromNewLogin; + } + + public Authentication getPrimaryAuthentication() { + return this.primaryAuthentication; + } + + public List getChainedAuthentications() { + return Collections.unmodifiableList(this.chainedAuthentications); + } + + public boolean isFromNewLogin() { + return this.fromNewLogin; + } + + public Service getService() { + return this.service; + } + + @Override + public boolean equals(final Object o) { + if (!(o instanceof Assertion)) { + return false; + } + + final Assertion a = (Assertion) o; + return this.primaryAuthentication.equals(a.getPrimaryAuthentication()) + && this.chainedAuthentications.equals(a.getChainedAuthentications()) + && this.service.equals(a.getService()) + && this.fromNewLogin == a.isFromNewLogin(); + } + + @Override + public int hashCode() { + final HashCodeBuilder builder = new HashCodeBuilder(15, 11); + builder.append(this.primaryAuthentication); + builder.append(this.chainedAuthentications); + builder.append(this.service); + builder.append(this.fromNewLogin); + return builder.toHashCode(); + } + + @Override + public String toString() { + return this.primaryAuthentication.toString() + ':' + this.service.toString(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/validation/package.html b/cas-server-core/src/main/java/org/jasig/cas/validation/package.html new file mode 100644 index 000000000000..9dfcf44fc365 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/validation/package.html @@ -0,0 +1,30 @@ + + + + +

Classes to perform additiona validation the Assertions provided by +the CAS server. It allows CAS to return basically a yes/no response +versus an array of information that the client would have to make a +decision on.

+ + + diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/DelegateController.java b/cas-server-core/src/main/java/org/jasig/cas/web/DelegateController.java new file mode 100644 index 000000000000..806277026ab0 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/DelegateController.java @@ -0,0 +1,50 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.servlet.mvc.AbstractController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Abstract class to be extended by all controllers that may become a delegate. + * All subclass must implement the canHandle method to say if they can handle a request or not. + * @author Frederic Esnault + * @since 3.5 + * @deprecated As of 4.1, the class is required to note its abstractness in the name and will be renamed in the future. + */ +@Deprecated +public abstract class DelegateController extends AbstractController { + + /** The logger. */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * Determine if a DelegateController subclass can handle the current request. + * @param request the current request + * @param response the response + * @return true if the controller can handler the request, false otherwise + */ + public abstract boolean canHandle(HttpServletRequest request, HttpServletResponse response); + + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/flow/AbstractNonInteractiveCredentialsAction.java b/cas-server-core/src/main/java/org/jasig/cas/web/flow/AbstractNonInteractiveCredentialsAction.java new file mode 100644 index 000000000000..3d8bb0683bf4 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/flow/AbstractNonInteractiveCredentialsAction.java @@ -0,0 +1,163 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.AuthenticationException; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.jasig.cas.authentication.principal.PrincipalFactory; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.ticket.TicketException; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.web.support.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; +import org.springframework.webflow.action.AbstractAction; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +import javax.validation.constraints.NotNull; + +/** + * Abstract class to handle the retrieval and authentication of non-interactive + * credential such as client certificates, NTLM, etc. + * + * @author Scott Battaglia + + * @since 3.0.0.4 + */ +public abstract class AbstractNonInteractiveCredentialsAction extends AbstractAction { + + /** The logger instance. */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * The Principal factory. + */ + protected PrincipalFactory principalFactory = new DefaultPrincipalFactory(); + + /** Instance of CentralAuthenticationService. */ + @NotNull + private CentralAuthenticationService centralAuthenticationService; + + /** + * Checks if is renew present. + * + * @param context the context + * @return true, if renew present + */ + protected final boolean isRenewPresent(final RequestContext context) { + return StringUtils.hasText(context.getRequestParameters().get("renew")); + } + + @Override + protected final Event doExecute(final RequestContext context) { + final Credential credential = constructCredentialsFromRequest(context); + + if (credential == null) { + return error(); + } + + final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context); + final Service service = WebUtils.getService(context); + + if (isRenewPresent(context) + && ticketGrantingTicketId != null + && service != null) { + + try { + final ServiceTicket serviceTicketId = this.centralAuthenticationService + .grantServiceTicket(ticketGrantingTicketId, + service, + credential); + WebUtils.putServiceTicketInRequestScope(context, serviceTicketId); + return result("warn"); + } catch (final AuthenticationException e) { + onError(context, credential); + return error(); + } catch (final TicketException e) { + this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId); + logger.debug("Attempted to generate a ServiceTicket using renew=true with different credential", e); + } + } + + try { + WebUtils.putTicketGrantingTicketInScopes( + context, + this.centralAuthenticationService + .createTicketGrantingTicket(credential)); + onSuccess(context, credential); + return success(); + } catch (final Exception e) { + onError(context, credential); + return error(); + } + } + + public final void setCentralAuthenticationService( + final CentralAuthenticationService centralAuthenticationService) { + this.centralAuthenticationService = centralAuthenticationService; + } + + /** + * Sets principal factory to create principal objects. + * + * @param principalFactory the principal factory + */ + public void setPrincipalFactory(final PrincipalFactory principalFactory) { + this.principalFactory = principalFactory; + } + + /** + * Hook method to allow for additional processing of the response before + * returning an error event. + * + * @param context the context for this specific request. + * @param credential the credential for this request. + */ + protected void onError(final RequestContext context, + final Credential credential) { + // default implementation does nothing + } + + /** + * Hook method to allow for additional processing of the response before + * returning a success event. + * + * @param context the context for this specific request. + * @param credential the credential for this request. + */ + protected void onSuccess(final RequestContext context, + final Credential credential) { + // default implementation does nothing + } + + /** + * Abstract method to implement to construct the credential from the + * request object. + * + * @param context the context for this request. + * @return the constructed credential or null if none could be constructed + * from the request. + */ + protected abstract Credential constructCredentialsFromRequest( + final RequestContext context); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/flow/AuthenticationExceptionHandler.java b/cas-server-core/src/main/java/org/jasig/cas/web/flow/AuthenticationExceptionHandler.java new file mode 100644 index 000000000000..2fe610af61d1 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/flow/AuthenticationExceptionHandler.java @@ -0,0 +1,132 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.authentication.AuthenticationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.binding.message.MessageBuilder; +import org.springframework.binding.message.MessageContext; + +import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Performs two important error handling functions on an {@link AuthenticationException} raised from the authentication + * layer: + * + *
    + *
  1. Maps handler errors onto message bundle strings for display to user.
  2. + *
  3. Determines the next webflow state by comparing handler erors against {@link #errors} + * in list order. The first entry that matches determines the outcome state, which + * is the simple class name of the exception.
  4. + *
+ * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class AuthenticationExceptionHandler { + + /** State name when no matching exception is found. */ + private static final String UNKNOWN = "UNKNOWN"; + + /** Default message bundle prefix. */ + private static final String DEFAULT_MESSAGE_BUNDLE_PREFIX = "authenticationFailure."; + + /** Default list of errors this class knows how to handle. */ + private static final List> DEFAULT_ERROR_LIST = + new ArrayList<>(); + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + static { + DEFAULT_ERROR_LIST.add(javax.security.auth.login.AccountLockedException.class); + DEFAULT_ERROR_LIST.add(javax.security.auth.login.FailedLoginException.class); + DEFAULT_ERROR_LIST.add(javax.security.auth.login.CredentialExpiredException.class); + DEFAULT_ERROR_LIST.add(javax.security.auth.login.AccountNotFoundException.class); + DEFAULT_ERROR_LIST.add(org.jasig.cas.authentication.AccountDisabledException.class); + DEFAULT_ERROR_LIST.add(org.jasig.cas.authentication.InvalidLoginLocationException.class); + DEFAULT_ERROR_LIST.add(org.jasig.cas.authentication.AccountPasswordMustChangeException.class); + DEFAULT_ERROR_LIST.add(org.jasig.cas.authentication.InvalidLoginTimeException.class); + } + + /** Ordered list of error classes that this class knows how to handle. */ + @NotNull + private List> errors = DEFAULT_ERROR_LIST; + + /** String appended to exception class name to create a message bundle key for that particular error. */ + private String messageBundlePrefix = DEFAULT_MESSAGE_BUNDLE_PREFIX; + + /** + * Sets the list of errors that this class knows how to handle. + * + * @param errors List of errors in order of descending precedence. + */ + public void setErrors(final List> errors) { + this.errors = errors; + } + + public final List> getErrors() { + return Collections.unmodifiableList(this.errors); + } + + /** + * Sets the message bundle prefix appended to exception class names to create a message bundle key for that + * particular error. + * + * @param prefix Prefix appended to exception names. + */ + public void setMessageBundlePrefix(final String prefix) { + this.messageBundlePrefix = prefix; + } + + /** + * Maps an authentication exception onto a state name equal to the simple class name of the. + * + * @param e Authentication error to handle. + * @param messageContext the spring message context + * @return Name of next flow state to transition to or {@value #UNKNOWN} + * {@link org.jasig.cas.authentication.AuthenticationException#getHandlerErrors()} with highest precedence. + * Also sets an ERROR severity message in the message context of the form + * [messageBundlePrefix][exceptionClassSimpleName] for each handler error that is + * configured. If not match is found, {@value #UNKNOWN} is returned. + */ + public String handle(final AuthenticationException e, final MessageContext messageContext) { + if (e != null) { + final MessageBuilder builder = new MessageBuilder(); + for (final Class kind : this.errors) { + for (final Class handlerError : e.getHandlerErrors().values()) { + if (handlerError != null && handlerError.equals(kind)) { + final String handlerErrorName = handlerError.getSimpleName(); + final String messageCode = this.messageBundlePrefix + handlerErrorName; + messageContext.addMessage(builder.error().code(messageCode).build()); + return handlerErrorName; + } + } + + } + } + final String messageCode = this.messageBundlePrefix + UNKNOWN; + logger.trace("Unable to translate handler errors of the authentication exception {}. Returning {} by default...", e, messageCode); + messageContext.addMessage(new MessageBuilder().error().code(messageCode).build()); + return UNKNOWN; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/support/AbstractArgumentExtractor.java b/cas-server-core/src/main/java/org/jasig/cas/web/support/AbstractArgumentExtractor.java new file mode 100644 index 000000000000..649510b1ca0c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/support/AbstractArgumentExtractor.java @@ -0,0 +1,61 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.support; + +import javax.servlet.http.HttpServletRequest; + +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract class for handling argument extraction. + * + * @author Scott Battaglia + * @since 3.1.2 + * + */ +public abstract class AbstractArgumentExtractor implements ArgumentExtractor { + + /** + * Logger instance. + */ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + @Override + public final WebApplicationService extractService(final HttpServletRequest request) { + final WebApplicationService service = extractServiceInternal(request); + + if (service == null) { + logger.debug("Extractor did not generate service."); + } else { + logger.debug("Extractor generated service for: {}", service.getId()); + } + + return service; + } + + /** + * Extract service from the request. + * + * @param request the request + * @return the web application service + */ + protected abstract WebApplicationService extractServiceInternal(final HttpServletRequest request); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/support/CasArgumentExtractor.java b/cas-server-core/src/main/java/org/jasig/cas/web/support/CasArgumentExtractor.java new file mode 100644 index 000000000000..e4eac1300342 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/support/CasArgumentExtractor.java @@ -0,0 +1,38 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.support; + +import javax.servlet.http.HttpServletRequest; + +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.authentication.principal.WebApplicationService; + +/** + * Implements the traditional CAS2 protocol. + * + * @author Scott Battaglia + * @since 3.1 + */ +public final class CasArgumentExtractor extends AbstractArgumentExtractor { + + @Override + public WebApplicationService extractServiceInternal(final HttpServletRequest request) { + return SimpleWebApplicationServiceImpl.createServiceFrom(request); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/support/WebUtils.java b/cas-server-core/src/main/java/org/jasig/cas/web/support/WebUtils.java new file mode 100644 index 000000000000..cb4db93b6702 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/support/WebUtils.java @@ -0,0 +1,314 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.support; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.logout.LogoutRequest; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.springframework.util.Assert; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.core.collection.MutableAttributeMap; +import org.springframework.webflow.execution.RequestContext; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * Common utilities for the web tier. + * + * @author Scott Battaglia + * @since 3.1 + */ +public final class WebUtils { + /** Request attribute that contains message key describing details of authorization failure.*/ + public static final String CAS_ACCESS_DENIED_REASON = "CAS_ACCESS_DENIED_REASON"; + + /** + * Instantiates a new web utils instance. + */ + private WebUtils() {} + + /** + * Gets the http servlet request from the context. + * + * @param context the context + * @return the http servlet request + */ + public static HttpServletRequest getHttpServletRequest( + final RequestContext context) { + Assert.isInstanceOf(ServletExternalContext.class, context + .getExternalContext(), + "Cannot obtain HttpServletRequest from event of type: " + + context.getExternalContext().getClass().getName()); + + return (HttpServletRequest) context.getExternalContext().getNativeRequest(); + } + + /** + * Gets the http servlet response. + * + * @param context the context + * @return the http servlet response + */ + public static HttpServletResponse getHttpServletResponse( + final RequestContext context) { + Assert.isInstanceOf(ServletExternalContext.class, context + .getExternalContext(), + "Cannot obtain HttpServletResponse from event of type: " + + context.getExternalContext().getClass().getName()); + return (HttpServletResponse) context.getExternalContext() + .getNativeResponse(); + } + + /** + * Gets the service from the request based on given extractors. + * + * @param argumentExtractors the argument extractors + * @param request the request + * @return the service, or null. + */ + public static WebApplicationService getService( + final List argumentExtractors, + final HttpServletRequest request) { + for (final ArgumentExtractor argumentExtractor : argumentExtractors) { + final WebApplicationService service = argumentExtractor + .extractService(request); + + if (service != null) { + return service; + } + } + + return null; + } + + /** + * Gets the service. + * + * @param argumentExtractors the argument extractors + * @param context the context + * @return the service + */ + public static WebApplicationService getService( + final List argumentExtractors, + final RequestContext context) { + final HttpServletRequest request = WebUtils.getHttpServletRequest(context); + return getService(argumentExtractors, request); + } + + /** + * Gets the service from the flow scope. + * + * @param context the context + * @return the service + */ + public static WebApplicationService getService(final RequestContext context) { + return context != null ? (WebApplicationService) context.getFlowScope().get("service") : null; + } + + /** + * Gets the registered service from the flow scope. + * + * @param context the context + * @return the service + */ + public static RegisteredService getRegisteredService(final RequestContext context) { + return context != null ? (RegisteredService) context.getFlowScope().get("registeredService") : null; + } + + /** + * Put ticket granting ticket in request and flow scopes. + * + * @param context the context + * @param ticket the ticket value + */ + public static void putTicketGrantingTicketInScopes( + final RequestContext context, @NotNull final TicketGrantingTicket ticket) { + final String ticketValue = ticket != null ? ticket.getId() : null; + putTicketGrantingTicketInScopes(context, ticketValue); + } + + /** + * Put ticket granting ticket in request and flow scopes. + * + * @param context the context + * @param ticketValue the ticket value + */ + public static void putTicketGrantingTicketInScopes( + final RequestContext context, @NotNull final String ticketValue) { + putTicketGrantingTicketIntoMap(context.getRequestScope(), ticketValue); + putTicketGrantingTicketIntoMap(context.getFlowScope(), ticketValue); + } + + /** + * Put ticket granting ticket into map that is either backed by the flow/request scope. + * Will override the previous value and blank out the setting if value is null or empty. + * @param map the map + * @param ticketValue the ticket value + */ + private static void putTicketGrantingTicketIntoMap(final MutableAttributeMap map, + @NotNull final String ticketValue) { + map.put("ticketGrantingTicketId", ticketValue); + } + + /** + * Gets the ticket granting ticket id from the request and flow scopes. + * + * @param context the context + * @return the ticket granting ticket id + */ + public static String getTicketGrantingTicketId( + @NotNull final RequestContext context) { + final String tgtFromRequest = (String) context.getRequestScope().get("ticketGrantingTicketId"); + final String tgtFromFlow = (String) context.getFlowScope().get("ticketGrantingTicketId"); + + return tgtFromRequest != null ? tgtFromRequest : tgtFromFlow; + + } + + /** + * Put service ticket in request scope. + * + * @param context the context + * @param ticketValue the ticket value + */ + public static void putServiceTicketInRequestScope( + final RequestContext context, final ServiceTicket ticketValue) { + context.getRequestScope().put("serviceTicketId", ticketValue.getId()); + } + + /** + * Gets the service ticket from request scope. + * + * @param context the context + * @return the service ticket from request scope + */ + public static String getServiceTicketFromRequestScope( + final RequestContext context) { + return context.getRequestScope().getString("serviceTicketId"); + } + + /** + * Put login ticket into flow scope. + * + * @param context the context + * @param ticket the ticket + */ + public static void putLoginTicket(final RequestContext context, final String ticket) { + context.getFlowScope().put("loginTicket", ticket); + } + + /** + * Gets the login ticket from flow scope. + * + * @param context the context + * @return the login ticket from flow scope + */ + public static String getLoginTicketFromFlowScope(final RequestContext context) { + // Getting the saved LT destroys it in support of one-time-use + // See section 3.5.1 of http://www.jasig.org/cas/protocol + final String lt = (String) context.getFlowScope().remove("loginTicket"); + return lt != null ? lt : ""; + } + + /** + * Gets the login ticket from request. + * + * @param context the context + * @return the login ticket from request + */ + public static String getLoginTicketFromRequest(final RequestContext context) { + return context.getRequestParameters().get("lt"); + } + + /** + * Put logout requests into flow scope. + * + * @param context the context + * @param requests the requests + */ + public static void putLogoutRequests(final RequestContext context, final List requests) { + context.getFlowScope().put("logoutRequests", requests); + } + + /** + * Gets the logout requests from flow scope. + * + * @param context the context + * @return the logout requests + */ + public static List getLogoutRequests(final RequestContext context) { + return (List) context.getFlowScope().get("logoutRequests"); + } + + /** + * Put service into flowscope. + * + * @param context the context + * @param service the service + */ + public static void putService(final RequestContext context, final Service service) { + context.getFlowScope().put("service", service); + } + + /** + * Put warning cookie value into flowscope. + * + * @param context the context + * @param cookieValue the cookie value + */ + public static void putWarningCookie(final RequestContext context, final Boolean cookieValue) { + context.getFlowScope().put("warnCookieValue", cookieValue); + } + + /** + * Put registered service into flowscope. + * + * @param context the context + * @param registeredService the service + */ + public static void putRegisteredService(final RequestContext context, + final RegisteredService registeredService) { + context.getFlowScope().put("registeredService", registeredService); + } + + /** + * Gets credential from the context. + * + * @param context the context + * @return the credential, or null if it cant be found in the context or if it has no id. + */ + public static Credential getCredential(@NotNull final RequestContext context) { + final Credential cFromRequest = (Credential) context.getRequestScope().get("credential"); + final Credential cFromFlow = (Credential) context.getFlowScope().get("credential"); + + final Credential credential = cFromRequest != null ? cFromRequest : cFromFlow; + if (credential != null && StringUtils.isBlank(credential.getId())) { + return null; + } + return credential; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/view/AbstractCasView.java b/cas-server-core/src/main/java/org/jasig/cas/web/view/AbstractCasView.java new file mode 100644 index 000000000000..4a367e5cf17c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/view/AbstractCasView.java @@ -0,0 +1,260 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.view; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.RememberMeCredential; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.validation.Assertion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.servlet.view.AbstractView; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Abstract class to handle retrieving the Assertion from the model. + * + * @author Scott Battaglia + * @since 3.1 + */ +public abstract class AbstractCasView extends AbstractView { + + /** + * Indicate whether this view will be generating the success response or not. + * By default, the view is treated as a failure. + */ + protected boolean successResponse; + + /** Logger instance. **/ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * Gets the assertion from the model. + * + * @param model the model + * @return the assertion from + */ + protected final Assertion getAssertionFrom(final Map model) { + return (Assertion) model.get(CasViewConstants.MODEL_ATTRIBUTE_NAME_ASSERTION); + } + + /** + * Gets the PGT from the model. + * + * @param model the model + * @return the pgt id + */ + protected final String getProxyGrantingTicketId(final Map model) { + return (String) model.get(CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET); + } + + /** + * Gets the authentication from the model. + * + * @param model the model + * @return the assertion from + * @since 4.1.0 + */ + protected final Authentication getPrimaryAuthenticationFrom(final Map model) { + return getAssertionFrom(model).getPrimaryAuthentication(); + } + + /** + * Gets an authentication attribute from the primary authentication object. + * + * @param model the model + * @param attributeName the attribute name + * @return the authentication attribute + */ + protected final String getAuthenticationAttribute(final Map model, final String attributeName) { + final Authentication authn = getPrimaryAuthenticationFrom(model); + return (String) authn.getAttributes().get(attributeName); + } + /** + * Gets the principal from the model. + * + * @param model the model + * @return the assertion from + * @since 4.1.0 + */ + protected final Principal getPrincipal(final Map model) { + return getPrimaryAuthenticationFrom(model).getPrincipal(); + } + + /** + * Gets principal attributes. + * Single-valued attributes are converted to a collection + * so the review can easily loop through all. + * @param model the model + * @return the attributes + * @see #convertAttributeValuesToMultiValuedObjects(java.util.Map) + * @since 4.1.0 + */ + protected final Map getPrincipalAttributesAsMultiValuedAttributes(final Map model) { + return convertAttributeValuesToMultiValuedObjects(getPrincipal(model).getAttributes()); + } + + /** + * Gets authentication attributes. + * Single-valued attributes are converted to a collection + * so the review can easily loop through all. + * @param model the model + * @return the attributes + * @see #convertAttributeValuesToMultiValuedObjects(java.util.Map) + * @since 4.1.0 + */ + protected final Map getAuthenticationAttributesAsMultiValuedAttributes(final Map model) { + return convertAttributeValuesToMultiValuedObjects(getPrimaryAuthenticationFrom(model).getAttributes()); + } + + /** + * Is remember me authentication? + * looks at the authentication object to find {@link RememberMeCredential#AUTHENTICATION_ATTRIBUTE_REMEMBER_ME} + * and expects the assertion to also note a new login session. + * @param model the model + * @return true if remember-me, false if otherwise. + */ + protected final boolean isRememberMeAuthentication(final Map model) { + final Map authnAttributes = getAuthenticationAttributesAsMultiValuedAttributes(model); + final Collection authnMethod = (Collection) authnAttributes.get(RememberMeCredential.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME); + return authnMethod != null && authnMethod.contains(Boolean.TRUE) && isAssertionBackedByNewLogin(model); + } + + + /** + * Is assertion backed by new login? + * + * @param model the model + * @return true/false. + */ + protected final boolean isAssertionBackedByNewLogin(final Map model) { + return getAssertionFrom(model).isFromNewLogin(); + } + + /** + * Convert attribute values to multi valued objects. + * + * @param attributes the attributes + * @return the map of attributes to return + */ + private Map convertAttributeValuesToMultiValuedObjects(final Map attributes) { + final Map attributesToReturn = new HashMap<>(); + final Set> entries = attributes.entrySet(); + for (final Map.Entry entry : entries) { + final Object value = entry.getValue(); + if (value instanceof Collection || value instanceof Map || value instanceof Object[] + || value instanceof Iterator || value instanceof Enumeration) { + attributesToReturn.put(entry.getKey(), value); + } else { + attributesToReturn.put(entry.getKey(), Collections.singleton(value)); + } + } + return attributesToReturn; + } + + /** + * Gets authentication date. + * + * @param model the model + * @return the authentication date + * @since 4.1.0 + */ + protected final Date getAuthenticationDate(final Map model) { + return getPrimaryAuthenticationFrom(model).getAuthenticationDate(); + } + + /** + * Gets validated service from the model. + * + * @param model the model + * @return the validated service from + */ + protected final Service getServiceFrom(final Map model) { + return (Service) model.get(CasViewConstants.MODEL_ATTRIBUTE_NAME_SERVICE); + } + + /** + * Gets chained authentications. + * + * @param model the model + * @return the chained authentications + */ + protected final Collection getChainedAuthentications(final Map model) { + final List chainedAuthenticationsToReturn = new ArrayList<>(); + + final Assertion assertion = getAssertionFrom(model); + final List chainedAuthentications = assertion.getChainedAuthentications(); + + /** + * Note that the last index in the list always describes the primary authentication + * event. All others in the chain should denote proxies. Per the CAS protocol, + * when authentication has proceeded through multiple proxies, + * the order in which the proxies were traversed MUST be reflected in the response. + * The most recently-visited proxy MUST be the first proxy listed, and all the + * other proxies MUST be shifted down as new proxies are added. I + */ + final int numberAuthenticationsExceptPrimary = chainedAuthentications.size() - 1; + for (int i = 0; i < numberAuthenticationsExceptPrimary; i++) { + chainedAuthenticationsToReturn.add(chainedAuthentications.get(i)); + } + return chainedAuthenticationsToReturn; + } + + /** + * Put into model. + * + * @param model the model + * @param key the key + * @param value the value + */ + protected final void putIntoModel(final Map model, final String key, final Object value){ + model.put(key, value); + } + + /** + * Put all into model. + * + * @param model the model + * @param values the values + */ + protected final void putAllIntoModel(final Map model, final Map values){ + model.putAll(values); + } + + /** + * Sets whether this view functions as a success response. + * + * @param successResponse the success response + * @since 4.1.0 + */ + public final void setSuccessResponse(final boolean successResponse) { + this.successResponse = successResponse; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/view/AbstractDelegatingCasView.java b/cas-server-core/src/main/java/org/jasig/cas/web/view/AbstractDelegatingCasView.java new file mode 100644 index 000000000000..9b4cf520a455 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/view/AbstractDelegatingCasView.java @@ -0,0 +1,68 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.view; + +import org.springframework.web.servlet.View; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +/** + * Renders and prepares CAS2 views. This view is responsible + * to simply just prep the base model, and delegates to + * a the real view to render the final output. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public abstract class AbstractDelegatingCasView extends AbstractCasView { + private final View view; + + /** + * Instantiates a new Abstract cas jstl view. + * + * @param view the view + */ + protected AbstractDelegatingCasView(final View view) { + this.view = view; + } + + @Override + protected void renderMergedOutputModel(final Map model, final HttpServletRequest request, + final HttpServletResponse response) throws Exception { + logger.debug("Preparing the output model to render view..."); + prepareMergedOutputModel(model, request, response); + + logger.trace("Prepared output model with objects [{}]. Now rendering view...", + model.keySet().toArray()); + this.view.render(model, request, response); + } + + /** + * Prepare merged output model before final rendering. + * + * @param model the model + * @param request the request + * @param response the response + * @throws Exception the exception + */ + protected abstract void prepareMergedOutputModel(final Map model, final HttpServletRequest request, + final HttpServletResponse response) throws Exception; + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/view/CasViewConstants.java b/cas-server-core/src/main/java/org/jasig/cas/web/view/CasViewConstants.java new file mode 100644 index 000000000000..42dd242a7683 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/view/CasViewConstants.java @@ -0,0 +1,67 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.web.view; + +import org.jasig.cas.CasProtocolConstants; + +/** + * Constants interface to host fields + * related to view rendering and validation model. + * @author Misagh Moayyed + * @since 4.1 + */ +public interface CasViewConstants { + + /** + * Represents the flag to note the principal credential used to establish + * a successful authentication event. + */ + String MODEL_ATTRIBUTE_NAME_PRINCIPAL_CREDENTIAL = "credential"; + + /** + * Represents the + * {@link org.jasig.cas.authentication.principal.Principal} object in the view. + */ + String MODEL_ATTRIBUTE_NAME_PRINCIPAL = "principal"; + + /** + * Represents the chained authentication objects + * in the view for proxying. + */ + String MODEL_ATTRIBUTE_NAME_CHAINED_AUTHENTICATIONS = "chainedAuthentications"; + + /** + * Represents the + * {@link org.jasig.cas.authentication.Authentication} object in the view. + **/ + String MODEL_ATTRIBUTE_NAME_PRIMARY_AUTHENTICATION = "primaryAuthentication"; + + /** Constant representing the Assertion in the cas validation model. */ + String MODEL_ATTRIBUTE_NAME_ASSERTION = "assertion"; + + /** The constant representing the validated service in the response. */ + String MODEL_ATTRIBUTE_NAME_SERVICE = "service"; + + /** The constant representing the PGTIOU in the response. */ + String MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET_IOU = CasProtocolConstants.VALIDATION_CAS_MODEL_PROXY_GRANTING_TICKET_IOU; + + /** The constant representing the PGT in the response. */ + String MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET = CasProtocolConstants.VALIDATION_CAS_MODEL_PROXY_GRANTING_TICKET; +} diff --git a/cas-server-core/src/main/java/org/slf4j/impl/CasDelegatingLogger.java b/cas-server-core/src/main/java/org/slf4j/impl/CasDelegatingLogger.java new file mode 100644 index 000000000000..f36a1fe541f9 --- /dev/null +++ b/cas-server-core/src/main/java/org/slf4j/impl/CasDelegatingLogger.java @@ -0,0 +1,439 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.slf4j.impl; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.slf4j.Logger; +import org.slf4j.Marker; +import org.slf4j.helpers.MarkerIgnoringBase; + +import java.io.PrintWriter; +import java.io.Serializable; +import java.io.StringWriter; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * The CAS logger wrapper, which uses a substitute logger to route the logs. + * This component only exists to intercept logging calls before they are + * sent to the logging engine (log4j, etc) and serves to manipulate + * logging messages if needed, such as removing sensitive ticket id from + * the log message. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class CasDelegatingLogger extends MarkerIgnoringBase implements Serializable { + + private static final long serialVersionUID = 6182834493563598289L; + + private static final Pattern TICKET_ID_PATTERN = Pattern.compile("(" + TicketGrantingTicket.PREFIX + "|" + + TicketGrantingTicket.PROXY_GRANTING_TICKET_IOU_PREFIX + "|" + TicketGrantingTicket.PROXY_GRANTING_TICKET_PREFIX + + ")(-)*(\\w)*(-)*(\\w)*"); + + /** + * Specifies the ending tail length of the ticket id that would still be visible in the output + * for troubleshooting purposes. + */ + private static final int VISIBLE_ID_TAIL_LENGTH = 10; + + private final Logger delegate; + + /** + * Instantiates a new Cas delegating logger. + * Used for serialization purposes only. + */ + private CasDelegatingLogger() { + this.delegate = null; + } + + /** + * Instantiates a new Cas delegating logger. + * + * @param delegate the delegate + */ + public CasDelegatingLogger(final Logger delegate) { + this.delegate = delegate; + } + + /** + * Manipulate the log message. For now, removes ticket ids from the log. + * @param msg log message + * @return message to log + */ + private String manipulateLogMessage(final String msg) { + return removeTicketId(msg); + } + + /** + * Manipulate the log arguments. For now, removes ticket ids from the log. + * @param args log args + * @return sanitized arguments + */ + private Object[] manipulateLogArguments(final Object... args) { + for (int i = 0; i < args.length; i++) { + if (args[i] != null) { + args[i] = removeTicketId(args[i].toString()); + } + } + return args; + } + + /** + * Remove ticket id from the log message. + * + * @param msg the message + * @return the modified message with tgt id removed + */ + private String removeTicketId(final String msg) { + String modifiedMessage = msg; + + if (StringUtils.isNotBlank(msg)) { + final Matcher matcher = TICKET_ID_PATTERN.matcher(msg); + while (matcher.find()) { + final String match = matcher.group(); + final String newId = matcher.group(1) + '-' + + StringUtils.repeat("*", match.length() - VISIBLE_ID_TAIL_LENGTH) + + StringUtils.right(match, VISIBLE_ID_TAIL_LENGTH); + + modifiedMessage = modifiedMessage.replaceAll(match, newId); + } + } + return modifiedMessage; + } + + /** + * Gets exception to log. + * + * @param msg the error msg + * @param t the exception to log. May be null if + * the underlying call did not specify an inner exception. + * @return the exception message to log + */ + private String getExceptionToLog(final String msg, final Throwable t) { + final StringWriter sW = new StringWriter(); + final PrintWriter w = new PrintWriter(sW); + w.println(manipulateLogMessage(msg)); + if (t != null) { + t.printStackTrace(w); + } + + final String log = sW.getBuffer().toString(); + return manipulateLogMessage(log); + } + + /* + * TRACE level logging + */ + @Override + public void trace(final String format, final Object arg) { + delegate.trace(manipulateLogMessage(format), manipulateLogArguments(arg)); + } + + @Override + public void trace(final String format, final Object arg1, final Object arg2) { + delegate.trace(manipulateLogMessage(format), manipulateLogArguments(arg1, arg2)); + } + + @Override + public void trace(final String format, final Object... arguments) { + delegate.trace(manipulateLogMessage(format), manipulateLogArguments(arguments)); + } + + @Override + public void trace(final String msg, final Throwable t) { + delegate.trace(getExceptionToLog(msg, t)); + } + + @Override + public void trace(final Marker marker, final String msg) { + delegate.trace(marker, manipulateLogMessage(msg)); + } + + @Override + public void trace(final Marker marker, final String format, final Object arg) { + delegate.trace(marker, manipulateLogMessage(format), manipulateLogArguments(arg)); + } + + @Override + public void trace(final Marker marker, final String format, final Object arg1, final Object arg2) { + delegate.trace(marker, manipulateLogMessage(format), manipulateLogArguments(arg1, arg2)); + } + + @Override + public void trace(final Marker marker, final String format, final Object... arguments) { + delegate.trace(marker, manipulateLogMessage(format), manipulateLogArguments(arguments)); + } + + @Override + public void trace(final Marker marker, final String msg, final Throwable t) { + delegate.trace(marker, getExceptionToLog(msg, t)); + } + + @Override + public void trace(final String msg) { + delegate.trace(manipulateLogMessage(msg)); + } + + @Override + public boolean isTraceEnabled() { + return delegate.isTraceEnabled(); + } + + /* + * DEBUG level logging + */ + + @Override + public boolean isDebugEnabled() { + return delegate.isDebugEnabled(); + } + + @Override + public void debug(final String format, final Object arg) { + delegate.debug(manipulateLogMessage(format), manipulateLogArguments(arg)); + } + + @Override + public void debug(final String format, final Object arg1, final Object arg2) { + delegate.debug(manipulateLogMessage(format), manipulateLogArguments(arg1, arg2)); + } + + @Override + public void debug(final String format, final Object... arguments) { + delegate.debug(manipulateLogMessage(format), manipulateLogArguments(arguments)); + } + + @Override + public void debug(final String msg, final Throwable t) { + delegate.debug(getExceptionToLog(msg, t)); + } + + @Override + public void debug(final Marker marker, final String msg) { + delegate.debug(marker, manipulateLogMessage(msg)); + } + + @Override + public void debug(final Marker marker, final String format, final Object arg) { + delegate.debug(marker, manipulateLogMessage(format), manipulateLogArguments(arg)); + } + + @Override + public void debug(final Marker marker, final String format, final Object arg1, final Object arg2) { + delegate.debug(marker, manipulateLogMessage(format), manipulateLogArguments(arg1, arg2)); + } + + @Override + public void debug(final Marker marker, final String format, final Object... arguments) { + delegate.debug(marker, manipulateLogMessage(format), manipulateLogArguments(arguments)); + } + + @Override + public void debug(final Marker marker, final String msg, final Throwable t) { + delegate.debug(marker, getExceptionToLog(msg, t)); + } + + @Override + public void debug(final String msg) { + delegate.debug(manipulateLogMessage(msg)); + } + + /* + * INFO level logging + */ + + @Override + public boolean isInfoEnabled() { + return delegate.isInfoEnabled(); + } + + @Override + public void info(final String format, final Object arg) { + delegate.info(manipulateLogMessage(format), manipulateLogArguments(arg)); + } + + @Override + public void info(final String format, final Object arg1, final Object arg2) { + delegate.info(manipulateLogMessage(format), manipulateLogArguments(arg1, arg2)); + } + + @Override + public void info(final String format, final Object... arguments) { + delegate.info(manipulateLogMessage(format), manipulateLogArguments(arguments)); + } + + @Override + public void info(final String msg, final Throwable t) { + delegate.info(getExceptionToLog(msg, t)); + } + + @Override + public void info(final Marker marker, final String msg) { + delegate.info(marker, manipulateLogMessage(msg)); + } + + @Override + public void info(final Marker marker, final String format, final Object arg) { + delegate.info(marker, manipulateLogMessage(format), manipulateLogArguments(arg)); + } + + @Override + public void info(final Marker marker, final String format, final Object arg1, final Object arg2) { + delegate.info(marker, manipulateLogMessage(format), manipulateLogArguments(arg1, arg2)); + } + + @Override + public void info(final Marker marker, final String format, final Object... arguments) { + delegate.info(marker, manipulateLogMessage(format), manipulateLogArguments(arguments)); + } + + @Override + public void info(final Marker marker, final String msg, final Throwable t) { + delegate.info(marker, getExceptionToLog(msg, t)); + } + + @Override + public void info(final String msg) { + delegate.info(manipulateLogMessage(msg)); + } + + /* + * WARN level logging + */ + + @Override + public boolean isWarnEnabled() { + return delegate.isWarnEnabled(); + } + + @Override + public void warn(final String format, final Object arg) { + delegate.warn(manipulateLogMessage(format), manipulateLogArguments(arg)); + } + + @Override + public void warn(final String format, final Object arg1, final Object arg2) { + delegate.warn(manipulateLogMessage(format), manipulateLogArguments(arg1, arg2)); + } + + @Override + public void warn(final String format, final Object... arguments) { + delegate.warn(manipulateLogMessage(format), manipulateLogArguments(arguments)); + } + + @Override + public void warn(final String msg, final Throwable t) { + delegate.warn(getExceptionToLog(msg, t)); + } + + @Override + public void warn(final Marker marker, final String msg) { + delegate.warn(marker, manipulateLogMessage(msg)); + } + + @Override + public void warn(final Marker marker, final String format, final Object arg) { + delegate.warn(marker, manipulateLogMessage(format), manipulateLogArguments(arg)); + } + + @Override + public void warn(final Marker marker, final String format, final Object arg1, final Object arg2) { + delegate.warn(marker, manipulateLogMessage(format), manipulateLogArguments(arg1, arg2)); + } + + @Override + public void warn(final Marker marker, final String format, final Object... arguments) { + delegate.warn(marker, manipulateLogMessage(format), manipulateLogArguments(arguments)); + } + + @Override + public void warn(final Marker marker, final String msg, final Throwable t) { + delegate.warn(marker, getExceptionToLog(msg, t)); + } + + @Override + public void warn(final String msg) { + delegate.warn(manipulateLogMessage(msg)); + } + + /* + * ERROR level logging + */ + + @Override + public boolean isErrorEnabled() { + return delegate.isErrorEnabled(); + } + + @Override + public void error(final String format, final Object arg) { + delegate.error(manipulateLogMessage(format), manipulateLogArguments(arg)); + } + + @Override + public void error(final String format, final Object arg1, final Object arg2) { + delegate.error(manipulateLogMessage(format), manipulateLogArguments(arg1, arg2)); + } + + @Override + public void error(final String format, final Object... arguments) { + delegate.error(manipulateLogMessage(format), manipulateLogArguments(arguments)); + } + + @Override + public void error(final String msg, final Throwable t) { + delegate.error(getExceptionToLog(msg, t)); + } + + @Override + public void error(final Marker marker, final String msg) { + delegate.error(marker, manipulateLogMessage(msg)); + } + + @Override + public void error(final Marker marker, final String format, final Object arg) { + delegate.error(marker, manipulateLogMessage(format), manipulateLogArguments(arg)); + } + + @Override + public void error(final Marker marker, final String format, final Object arg1, final Object arg2) { + delegate.error(marker, manipulateLogMessage(format), manipulateLogArguments(arg1, arg2)); + } + + @Override + public void error(final Marker marker, final String format, final Object... arguments) { + delegate.error(marker, manipulateLogMessage(format), manipulateLogArguments(arguments)); + } + + @Override + public void error(final Marker marker, final String msg, final Throwable t) { + delegate.error(marker, getExceptionToLog(msg, t)); + } + + @Override + public void error(final String msg) { + delegate.error(manipulateLogMessage(msg)); + } + + @Override + public String getName() { + return "CAS Delegating Logger"; + } +} diff --git a/cas-server-core/src/main/java/org/slf4j/impl/CasLoggerFactory.java b/cas-server-core/src/main/java/org/slf4j/impl/CasLoggerFactory.java new file mode 100644 index 000000000000..df8b6c2920d9 --- /dev/null +++ b/cas-server-core/src/main/java/org/slf4j/impl/CasLoggerFactory.java @@ -0,0 +1,122 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.slf4j.impl; + +import org.apache.commons.lang3.StringUtils; +import org.reflections.Reflections; +import org.reflections.scanners.SubTypesScanner; +import org.reflections.util.ClasspathHelper; +import org.reflections.util.ConfigurationBuilder; +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; +import org.slf4j.helpers.NOPLogger; +import org.slf4j.helpers.Util; + +import java.net.URL; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * An implementation of {@link org.slf4j.ILoggerFactory} that is looked up via the + * {@link org.slf4j.impl.StaticLoggerBinder} of CAS itself. It is responsible for + * creating {@link org.slf4j.Logger} instances and passing them back to the slf4j engine. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class CasLoggerFactory implements ILoggerFactory { + + private static final String PACKAGE_TO_SCAN = "org.slf4j.impl"; + + private final Map loggerMap; + + private final Class realLoggerFactoryClass; + /** + * Instantiates a new Cas logger factory. + * Configures the reflection scanning engine to be prepared to scan org.slf4j.impl + * in order to find other available factories. + */ + public CasLoggerFactory() { + this.loggerMap = new ConcurrentHashMap<>(); + final Collection set = ClasspathHelper.forPackage(PACKAGE_TO_SCAN); + final Reflections reflections = new Reflections(new ConfigurationBuilder().addUrls(set).setScanners(new SubTypesScanner())); + + final Set> subTypesOf = reflections.getSubTypesOf(ILoggerFactory.class); + subTypesOf.remove(this.getClass()); + + if (subTypesOf.size() > 1) { + Util.report("Multiple ILoggerFactory bindings are found on the classpath:"); + for (final Class c : subTypesOf) { + Util.report("* " + c.getCanonicalName()); + } + } + + if (subTypesOf.isEmpty()) { + final RuntimeException e = new RuntimeException("No ILoggerFactory could be found on the classpath." + + " CAS cannot determine the logging framework." + + " Examine the project dependencies and ensure that there is one and only one logging framework available."); + + Util.report(e.getMessage(), e); + throw e; + } + this.realLoggerFactoryClass = subTypesOf.iterator().next(); + Util.report("ILoggerFactory to be used for logging is: " + this.realLoggerFactoryClass.getName()); + } + + /** + * {@inheritDoc} + *

Attempts to find the real Logger instance that + * is doing the heavy lifting and routes the request to an instance of + * {@link CasDelegatingLogger}. The instance is cached by the logger name.

+ */ + @Override + public Logger getLogger(final String name) { + if (StringUtils.isBlank(name)) { + return NOPLogger.NOP_LOGGER; + } + synchronized (loggerMap) { + if (!loggerMap.containsKey(name)) { + final Logger logger = getRealLoggerInstance(name); + loggerMap.put(name, new CasDelegatingLogger(logger)); + } + return loggerMap.get(name); + } + } + + /** + * Find the actual Logger instance that is available on the classpath. + * This is usually the logger adapter that is provided by the real logging framework, + * such as log4j, etc. The method will scan the runtime to find logger factories that + * are of type {@link org.slf4j.ILoggerFactory}. It will remove itself from this list + * first and then attempts to locate the next best factory from which real logger instances + * can be created. + * @param name requested logger name + * @return the logger instance created by the logger factory available on the classpath during runtime, or null. + */ + private Logger getRealLoggerInstance(final String name) { + try { + final ILoggerFactory factInstance = this.realLoggerFactoryClass.newInstance(); + return factInstance.getLogger(name); + } catch (final Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + } + +} diff --git a/cas-server-core/src/main/java/org/slf4j/impl/StaticLoggerBinder.java b/cas-server-core/src/main/java/org/slf4j/impl/StaticLoggerBinder.java new file mode 100644 index 000000000000..41388bbd5f02 --- /dev/null +++ b/cas-server-core/src/main/java/org/slf4j/impl/StaticLoggerBinder.java @@ -0,0 +1,69 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.slf4j.impl; + +import org.slf4j.ILoggerFactory; +import org.slf4j.spi.LoggerFactoryBinder; + +/** + * The static binder for slf4j logging, which allows CAS + * to select its own {@link org.slf4j.ILoggerFactory} instance at runtime. + * Note that this class MUST reside in the org.slf4j.impl + * package so it can be loaded by the runtime dynamic lookup. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class StaticLoggerBinder implements LoggerFactoryBinder { + + /** + * The unique instance of this class. + */ + private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder(); + + /** + * The {@link ILoggerFactory} instance returned by the + * {@link #getLoggerFactory} method should always be the same + * object. + */ + private final ILoggerFactory loggerFactory; + + /** + * Instantiates a new Static logger binder. + */ + private StaticLoggerBinder() { + this.loggerFactory = new CasLoggerFactory(); + } + + /** + * Return the singleton of this class. + * + * @return the StaticLoggerBinder singleton + */ + public static StaticLoggerBinder getSingleton() { + return SINGLETON; + } + + public ILoggerFactory getLoggerFactory() { + return this.loggerFactory; + } + + public String getLoggerFactoryClassStr() { + return CasLoggerFactory.class.getName(); + } +} diff --git a/cas-server-core/src/site/site.xml b/cas-server-core/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-core/src/site/site.xml @@ -0,0 +1,28 @@ + + + + +
+ + + + diff --git a/cas-server-3.4.2/cas-server-core/src/test/clover/clover.license b/cas-server-core/src/test/clover/clover.license similarity index 100% rename from cas-server-3.4.2/cas-server-core/src/test/clover/clover.license rename to cas-server-core/src/test/clover/clover.license diff --git a/cas-server-core/src/test/java/org/jasig/cas/AbstractCentralAuthenticationServiceTest.java b/cas-server-core/src/test/java/org/jasig/cas/AbstractCentralAuthenticationServiceTest.java new file mode 100644 index 000000000000..df6d094d59be --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/AbstractCentralAuthenticationServiceTest.java @@ -0,0 +1,73 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas; + +import org.jasig.cas.authentication.AuthenticationManager; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; + +/** + * @author Scott Battaglia + + * @since 3.0.0 + */ +@ContextConfiguration(locations = { + "/core-context.xml" +}) +public abstract class AbstractCentralAuthenticationServiceTest extends AbstractJUnit4SpringContextTests { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired(required = true) + private CentralAuthenticationService centralAuthenticationService; + + @Autowired(required = true) + private TicketRegistry ticketRegistry; + + @Autowired(required = true) + private AuthenticationManager authenticationManager; + + @Autowired(required = true) + private ServicesManager servicesManager; + + public AuthenticationManager getAuthenticationManager() { + return this.authenticationManager; + } + + public CentralAuthenticationService getCentralAuthenticationService() { + return this.centralAuthenticationService; + } + + public void setCentralAuthenticationService(final CentralAuthenticationService centralAuthenticationService) { + this.centralAuthenticationService = centralAuthenticationService; + } + + public TicketRegistry getTicketRegistry() { + return this.ticketRegistry; + } + + public ServicesManager getServicesManager() { + return this.servicesManager; + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/CentralAuthenticationServiceImplTests.java b/cas-server-core/src/test/java/org/jasig/cas/CentralAuthenticationServiceImplTests.java new file mode 100644 index 000000000000..d731de88e851 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/CentralAuthenticationServiceImplTests.java @@ -0,0 +1,485 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.AuthenticationException; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.MixedPrincipalException; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.services.UnauthorizedServiceException; +import org.jasig.cas.services.UnauthorizedServiceForPrincipalException; +import org.jasig.cas.services.UnauthorizedSsoServiceException; +import org.jasig.cas.ticket.TicketCreationException; +import org.jasig.cas.ticket.TicketException; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.jasig.cas.ticket.TicketState; +import org.jasig.cas.ticket.support.MultiTimeUseOrTimeoutExpirationPolicy; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.jasig.cas.validation.Assertion; +import org.jasig.cas.validation.Cas20WithoutProxyingValidationSpecification; +import org.jasig.cas.validation.ValidationSpecification; +import org.junit.Test; + +import java.util.Map; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class CentralAuthenticationServiceImplTests extends AbstractCentralAuthenticationServiceTest { + + @Test(expected=AuthenticationException.class) + public void verifyBadCredentialsOnTicketGrantingTicketCreation() throws Exception { + getCentralAuthenticationService().createTicketGrantingTicket( + TestUtils.getCredentialsWithDifferentUsernameAndPassword()); + } + + @Test + public void verifyGoodCredentialsOnTicketGrantingTicketCreation() throws Exception { + try { + assertNotNull(getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword())); + } catch (final TicketException e) { + fail(TestUtils.CONST_EXCEPTION_NON_EXPECTED); + } + } + + @Test + public void verifyDestroyTicketGrantingTicketWithNonExistantTicket() { + getCentralAuthenticationService().destroyTicketGrantingTicket("test"); + } + + @Test + public void verifyDestroyTicketGrantingTicketWithValidTicket() throws Exception { + final TicketGrantingTicket ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + getCentralAuthenticationService().destroyTicketGrantingTicket(ticketId.getId()); + } + + @Test(expected=TicketCreationException.class) + public void disallowNullCredentionalsWhenCreatingTicketGrantingTicket() throws Exception { + final TicketGrantingTicket ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket(null); + + } + + @Test(expected=TicketCreationException.class) + public void disallowNullCredentionalsArrayWhenCreatingTicketGrantingTicket() throws Exception { + final TicketGrantingTicket ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket(new Credential[] {null, null}); + } + + @Test(expected=ClassCastException.class) + public void verifyDestroyTicketGrantingTicketWithInvalidTicket() throws Exception { + final TicketGrantingTicket ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket serviceTicketId = getCentralAuthenticationService() + .grantServiceTicket(ticketId.getId(), TestUtils.getService()); + + getCentralAuthenticationService().destroyTicketGrantingTicket( + serviceTicketId.getId()); + + } + + @Test + public void checkGrantingOfServiceTicketUsingDefaultTicketIdGen() throws Exception { + final Service mockService = mock(Service.class); + when(mockService.getId()).thenReturn("testDefault"); + final TicketGrantingTicket ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket serviceTicketId = getCentralAuthenticationService() + .grantServiceTicket(ticketId.getId(), mockService); + assertNotNull(serviceTicketId); + } + + @Test + public void verifyGrantServiceTicketWithValidTicketGrantingTicket() throws Exception { + final TicketGrantingTicket ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + getCentralAuthenticationService().grantServiceTicket(ticketId.getId(), + TestUtils.getService()); + } + + @Test(expected=UnauthorizedServiceForPrincipalException.class) + public void verifyGrantServiceTicketFailsAuthzRule() throws Exception { + final TicketGrantingTicket ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + getCentralAuthenticationService().grantServiceTicket(ticketId.getId(), + TestUtils.getService("TestServiceAttributeForAuthzFails")); + } + + @Test + public void verifyGrantServiceTicketPassesAuthzRule() throws Exception { + final TicketGrantingTicket ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + getCentralAuthenticationService().grantServiceTicket(ticketId.getId(), + TestUtils.getService("TestServiceAttributeForAuthzPasses")); + } + + @Test + public void verifyGrantProxyTicketWithValidTicketGrantingTicket() throws Exception { + final TicketGrantingTicket ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket serviceTicketId = getCentralAuthenticationService() + .grantServiceTicket(ticketId.getId(), TestUtils.getService()); + final TicketGrantingTicket pgt = getCentralAuthenticationService().delegateTicketGrantingTicket( + serviceTicketId.getId(), TestUtils.getHttpBasedServiceCredentials()); + + final ServiceTicket pt = getCentralAuthenticationService().grantServiceTicket(pgt.getId(), + TestUtils.getService(), new Credential[] {}); + assertTrue(pt.getId().startsWith(ServiceTicket.PROXY_TICKET_PREFIX)); + } + + @Test(expected=TicketException.class) + public void verifyGrantServiceTicketWithInvalidTicketGrantingTicket() throws Exception { + final TicketGrantingTicket ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + getCentralAuthenticationService().destroyTicketGrantingTicket(ticketId.getId()); + getCentralAuthenticationService().grantServiceTicket(ticketId.getId(), + TestUtils.getService()); + } + + @Test(expected=TicketException.class) + public void verifyGrantServiceTicketWithExpiredTicketGrantingTicket() throws Exception { + ((CentralAuthenticationServiceImpl) getCentralAuthenticationService()).setTicketGrantingTicketExpirationPolicy( + new ExpirationPolicy() { + private static final long serialVersionUID = 1L; + + public boolean isExpired(final TicketState ticket) { + return true; + }}); + + final TicketGrantingTicket ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + try { + getCentralAuthenticationService().grantServiceTicket(ticketId.getId(), + TestUtils.getService()); + } finally { + ((CentralAuthenticationServiceImpl) getCentralAuthenticationService()).setTicketGrantingTicketExpirationPolicy( + new NeverExpiresExpirationPolicy()); + } +} + + @Test + public void verifyDelegateTicketGrantingTicketWithProperParams() throws Exception { + final TicketGrantingTicket ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket serviceTicketId = getCentralAuthenticationService() + .grantServiceTicket(ticketId.getId(), TestUtils.getService()); + final TicketGrantingTicket pgt = getCentralAuthenticationService().delegateTicketGrantingTicket( + serviceTicketId.getId(), TestUtils.getHttpBasedServiceCredentials()); + assertTrue(pgt.getId().startsWith(TicketGrantingTicket.PROXY_GRANTING_TICKET_PREFIX)); + } + + @Test(expected=TicketException.class) + public void verifyDelegateTicketGrantingTicketWithBadServiceTicket() throws Exception { + final TicketGrantingTicket ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket serviceTicketId = getCentralAuthenticationService() + .grantServiceTicket(ticketId.getId(), TestUtils.getService()); + getCentralAuthenticationService().destroyTicketGrantingTicket(ticketId.getId()); + getCentralAuthenticationService().delegateTicketGrantingTicket( + serviceTicketId.getId(), TestUtils.getHttpBasedServiceCredentials()); + } + + @Test + public void verifyGrantServiceTicketWithValidCredentials() throws Exception { + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + getCentralAuthenticationService().grantServiceTicket( + ticketGrantingTicket.getId(), TestUtils.getService(), + TestUtils.getCredentialsWithSameUsernameAndPassword()); + } + + @Test(expected=MixedPrincipalException.class) + public void verifyGrantServiceTicketWithDifferentCredentials() throws Exception { + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword("testA")); + getCentralAuthenticationService().grantServiceTicket( + ticketGrantingTicket.getId(), TestUtils.getService(), + TestUtils.getCredentialsWithSameUsernameAndPassword("testB")); + } + + @Test + public void verifyValidateServiceTicketWithExpires() throws Exception { + ((CentralAuthenticationServiceImpl) getCentralAuthenticationService()) + .setServiceTicketExpirationPolicy(new MultiTimeUseOrTimeoutExpirationPolicy( + 1, 1100)); + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket serviceTicket = getCentralAuthenticationService() + .grantServiceTicket(ticketGrantingTicket.getId(), TestUtils.getService()); + + getCentralAuthenticationService().validateServiceTicket(serviceTicket.getId(), + TestUtils.getService()); + + assertFalse(getTicketRegistry().deleteTicket(serviceTicket.getId())); + ((CentralAuthenticationServiceImpl) getCentralAuthenticationService()) + .setServiceTicketExpirationPolicy(new NeverExpiresExpirationPolicy()); + } + + @Test + public void verifyValidateServiceTicketWithValidService() throws Exception { + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket serviceTicket = getCentralAuthenticationService() + .grantServiceTicket(ticketGrantingTicket.getId(), TestUtils.getService()); + + getCentralAuthenticationService().validateServiceTicket(serviceTicket.getId(), + TestUtils.getService()); + } + + @Test(expected=UnauthorizedServiceException.class) + public void verifyValidateServiceTicketWithInvalidService() throws Exception { + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket serviceTicket = getCentralAuthenticationService() + .grantServiceTicket(ticketGrantingTicket.getId(), TestUtils.getService()); + + getCentralAuthenticationService().validateServiceTicket( + serviceTicket.getId(), TestUtils.getService("test2")); + } + + @Test(expected=TicketException.class) + public void verifyValidateServiceTicketWithInvalidServiceTicket() throws Exception { + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket serviceTicket = getCentralAuthenticationService() + .grantServiceTicket(ticketGrantingTicket.getId(), TestUtils.getService()); + getCentralAuthenticationService().destroyTicketGrantingTicket( + ticketGrantingTicket.getId()); + + getCentralAuthenticationService().validateServiceTicket( + serviceTicket.getId(), TestUtils.getService()); + } + + @Test(expected=TicketException.class) + public void verifyValidateServiceTicketNonExistantTicket() throws Exception { + getCentralAuthenticationService().validateServiceTicket("test", + TestUtils.getService()); + } + + @Test + public void verifyValidateServiceTicketWithoutUsernameAttribute() throws Exception { + final UsernamePasswordCredential cred = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket(cred); + final ServiceTicket serviceTicket = getCentralAuthenticationService().grantServiceTicket(ticketGrantingTicket.getId(), + TestUtils.getService()); + + final Assertion assertion = getCentralAuthenticationService().validateServiceTicket(serviceTicket.getId(), + TestUtils.getService()); + final Authentication auth = assertion.getPrimaryAuthentication(); + assertEquals(auth.getPrincipal().getId(), cred.getUsername()); + } + + @Test + public void verifyValidateServiceTicketWithDefaultUsernameAttribute() throws Exception { + final UsernamePasswordCredential cred = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket(cred); + + final Service svc = TestUtils.getService("testDefault"); + final ServiceTicket serviceTicket = getCentralAuthenticationService().grantServiceTicket(ticketGrantingTicket.getId(), svc); + + final Assertion assertion = getCentralAuthenticationService().validateServiceTicket(serviceTicket.getId(), svc); + final Authentication auth = assertion.getPrimaryAuthentication(); + assertEquals(auth.getPrincipal().getId(), cred.getUsername()); + } + + @Test + public void verifyValidateServiceTicketWithUsernameAttribute() throws Exception { + final UsernamePasswordCredential cred = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket(cred); + + final Service svc = TestUtils.getService("eduPersonTest"); + final ServiceTicket serviceTicket = getCentralAuthenticationService().grantServiceTicket(ticketGrantingTicket.getId(), svc); + + final Assertion assertion = getCentralAuthenticationService().validateServiceTicket(serviceTicket.getId(), svc); + assertEquals("developer", assertion.getPrimaryAuthentication().getPrincipal().getId()); + } + + @Test + public void verifyGrantServiceTicketWithCredsAndSsoFalse() throws Exception { + final UsernamePasswordCredential cred = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket(cred); + + final Service svc = TestUtils.getService("TestSsoFalse"); + final ServiceTicket serviceTicket = getCentralAuthenticationService().grantServiceTicket(ticketGrantingTicket.getId(), svc, cred); + assertNotNull(serviceTicket); + } + + @Test(expected= UnauthorizedSsoServiceException.class) + public void verifyGrantServiceTicketWithNoCredsAndSsoFalse() throws Exception { + final UsernamePasswordCredential cred = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket(cred); + + final Service svc = TestUtils.getService("TestSsoFalse"); + getCentralAuthenticationService().grantServiceTicket(ticketGrantingTicket.getId(), svc, null); + } + + @Test + public void verifyValidateServiceTicketNoAttributesReturned() throws Exception { + final Service service = TestUtils.getService(); + final UsernamePasswordCredential cred = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket(cred); + final ServiceTicket serviceTicket = getCentralAuthenticationService().grantServiceTicket(ticketGrantingTicket.getId(), + service); + + final Assertion assertion = getCentralAuthenticationService().validateServiceTicket(serviceTicket.getId(), + service); + final Authentication auth = assertion.getPrimaryAuthentication(); + assertEquals(0, auth.getPrincipal().getAttributes().size()); + } + + @Test + public void verifyValidateServiceTicketReturnAllAttributes() throws Exception { + final Service service = TestUtils.getService("eduPersonTest"); + final UsernamePasswordCredential cred = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket(cred); + final ServiceTicket serviceTicket = getCentralAuthenticationService().grantServiceTicket(ticketGrantingTicket.getId(), + service); + + final Assertion assertion = getCentralAuthenticationService().validateServiceTicket(serviceTicket.getId(), + service); + final Authentication auth = assertion.getPrimaryAuthentication(); + assertEquals(3, auth.getPrincipal().getAttributes().size()); + } + + @Test + public void verifyValidateServiceTicketReturnOnlyAllowedAttribute() throws Exception { + final Service service = TestUtils.getService("eduPersonTestInvalid"); + final UsernamePasswordCredential cred = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket(cred); + final ServiceTicket serviceTicket = getCentralAuthenticationService().grantServiceTicket(ticketGrantingTicket.getId(), + service); + + final Assertion assertion = getCentralAuthenticationService().validateServiceTicket(serviceTicket.getId(), + service); + final Authentication auth = assertion.getPrimaryAuthentication(); + final Map attributes = auth.getPrincipal().getAttributes(); + assertEquals(1, attributes.size()); + assertEquals("adopters", attributes.get("groupMembership")); + } + + @Test + public void verifyValidateServiceTicketAnonymous() throws Exception { + final Service service = TestUtils.getService("testAnonymous"); + final UsernamePasswordCredential cred = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket(cred); + final ServiceTicket serviceTicket = getCentralAuthenticationService().grantServiceTicket(ticketGrantingTicket.getId(), + service); + + final Assertion assertion = getCentralAuthenticationService().validateServiceTicket(serviceTicket.getId(), + service); + final Authentication auth = assertion.getPrimaryAuthentication(); + assertNotEquals(cred.getUsername(), auth.getPrincipal().getId()); + } + + @Test + public void verifyValidateServiceTicketWithInvalidUsernameAttribute() throws Exception { + final UsernamePasswordCredential cred = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket(cred); + + final Service svc = TestUtils.getService("eduPersonTestInvalid"); + final ServiceTicket serviceTicket = getCentralAuthenticationService().grantServiceTicket(ticketGrantingTicket.getId(), svc); + + final Assertion assertion = getCentralAuthenticationService().validateServiceTicket(serviceTicket.getId(), svc); + final Authentication auth = assertion.getPrimaryAuthentication(); + + /* + * The attribute specified for this service does not resolve. + * Therefore, we expect the default to be returned. + */ + assertEquals(auth.getPrincipal().getId(), cred.getUsername()); + } + + /** + * This test simulates : + * - a first authentication for a default service + * - a second authentication with the renew parameter and the same service (and same credentials) + * - a validation of the second ticket. + * + * When supplemental authentications were returned with the chained authentications, the validation specification + * failed as it only expects one authentication. Thus supplemental authentications should not be returned in the + * chained authentications. Both concepts are orthogonal. + * + * @throws org.jasig.cas.ticket.TicketException + * @throws AuthenticationException + */ + @Test + public void authenticateTwiceWithRenew() throws TicketException, AuthenticationException { + final CentralAuthenticationService cas = getCentralAuthenticationService(); + final Service svc = TestUtils.getService("testDefault"); + final UsernamePasswordCredential goodCredential = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final TicketGrantingTicket tgtId = cas.createTicketGrantingTicket(goodCredential); + cas.grantServiceTicket(tgtId.getId(), svc); + // simulate renew with new good same credentials + final ServiceTicket st2Id = cas.grantServiceTicket(tgtId.getId(), svc, goodCredential); + final Assertion assertion = cas.validateServiceTicket(st2Id.getId(), svc); + final ValidationSpecification validationSpecification = new Cas20WithoutProxyingValidationSpecification(); + assertTrue(validationSpecification.isSatisfiedBy(assertion)); + } + + /** + * This test checks that the TGT destruction happens properly for a remote registry. + * It previously failed when the deletion happens before the ticket was marked expired because an update was necessary for that. + * + * @throws AuthenticationException + * @throws org.jasig.cas.ticket.TicketException + */ + @Test + public void verifyDestroyRemoteRegistry() throws TicketException, AuthenticationException { + final MockOnlyOneTicketRegistry registry = new MockOnlyOneTicketRegistry(); + final TicketGrantingTicketImpl tgt = new TicketGrantingTicketImpl("TGT-1", mock(Authentication.class), + mock(ExpirationPolicy.class)); + final MockExpireUpdateTicketLogoutManager logoutManager = new MockExpireUpdateTicketLogoutManager(registry); + // consider authentication has happened and the TGT is in the registry + registry.addTicket(tgt); + // create a new CASimpl + final CentralAuthenticationServiceImpl cas = new CentralAuthenticationServiceImpl(registry, null, null, null, null, null, null, + null, logoutManager); + // destroy to mark expired and then delete : the opposite would fail with a "No ticket to update" error from the registry + cas.destroyTicketGrantingTicket(tgt.getId()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/CentralAuthenticationServiceImplWithMockitoTests.java b/cas-server-core/src/test/java/org/jasig/cas/CentralAuthenticationServiceImplWithMockitoTests.java new file mode 100644 index 000000000000..1bde8d7fe1e1 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/CentralAuthenticationServiceImplWithMockitoTests.java @@ -0,0 +1,264 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas; + +import org.apache.commons.collections4.functors.TruePredicate; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.AuthenticationHandler; +import org.jasig.cas.authentication.AuthenticationManager; +import org.jasig.cas.authentication.BasicCredentialMetaData; +import org.jasig.cas.authentication.CredentialMetaData; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.logout.LogoutManager; +import org.jasig.cas.services.DefaultRegisteredServiceAccessStrategy; +import org.jasig.cas.services.DefaultRegisteredServiceUsernameProvider; +import org.jasig.cas.services.RefuseRegisteredServiceProxyPolicy; +import org.jasig.cas.services.RegexMatchingRegisteredServiceProxyPolicy; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.RegisteredServiceProxyPolicy; +import org.jasig.cas.services.ReturnAllAttributeReleasePolicy; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.services.UnauthorizedProxyingException; +import org.jasig.cas.services.UnauthorizedServiceException; +import org.jasig.cas.ticket.TicketException; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.InvalidTicketException; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.jasig.cas.validation.Assertion; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentMatcher; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests with the help of Mockito framework. + * + * @author Dmitriy Kopylenko + * @since 3.0.0 + */ +public class CentralAuthenticationServiceImplWithMockitoTests { + private static final String TGT_ID = "tgt-id"; + private static final String TGT2_ID = "tgt2-id"; + + private static final String ST_ID = "st-id"; + private static final String ST2_ID = "st2-id"; + + private static final String SVC1_ID = "test1"; + private static final String SVC2_ID = "test2"; + + private static final String PRINCIPAL = "principal"; + + private CentralAuthenticationService cas; + private Authentication authentication; + private TicketRegistry ticketRegMock; + + private static class VerifyServiceByIdMatcher extends ArgumentMatcher { + private final String id; + + public VerifyServiceByIdMatcher(final String id) { + this.id = id; + } + + @Override + public boolean matches(final Object argument) { + final Service s = (Service) argument; + return s != null && s.getId().equals(this.id); + } + + } + + @Before + public void prepareNewCAS() { + this.authentication = mock(Authentication.class); + when(this.authentication.getAuthenticationDate()).thenReturn(new Date()); + final CredentialMetaData metadata = new BasicCredentialMetaData(TestUtils.getCredentialsWithSameUsernameAndPassword("principal")); + final Map successes = new HashMap<>(); + successes.put("handler1", new DefaultHandlerResult(mock(AuthenticationHandler.class), metadata)); + when(this.authentication.getCredentials()).thenReturn(Arrays.asList(metadata)); + when(this.authentication.getSuccesses()).thenReturn(successes); + when(this.authentication.getPrincipal()).thenReturn(new DefaultPrincipalFactory().createPrincipal(PRINCIPAL)); + + final Service service1 = TestUtils.getService(SVC1_ID); + final ServiceTicket stMock = createMockServiceTicket(ST_ID, service1); + + final TicketGrantingTicket tgtRootMock = createRootTicketGrantingTicket(); + + final TicketGrantingTicket tgtMock = createMockTicketGrantingTicket(TGT_ID, stMock, false, + tgtRootMock, new ArrayList()); + when(tgtMock.getProxiedBy()).thenReturn(TestUtils.getService("proxiedBy")); + + final List authnListMock = mock(List.class); + //Size is required to be 2, so that we can simulate proxying capabilities + when(authnListMock.size()).thenReturn(2); + when(authnListMock.get(anyInt())).thenReturn(this.authentication); + when(tgtMock.getChainedAuthentications()).thenReturn(authnListMock); + when(stMock.getGrantingTicket()).thenReturn(tgtMock); + + final Service service2 = TestUtils.getService(SVC2_ID); + final ServiceTicket stMock2 = createMockServiceTicket(ST2_ID, service2); + + final TicketGrantingTicket tgtMock2 = createMockTicketGrantingTicket(TGT2_ID, stMock2, false, tgtRootMock, authnListMock); + + //Mock TicketRegistry + this.ticketRegMock = mock(TicketRegistry.class); + when(ticketRegMock.getTicket(eq(tgtMock.getId()), eq(TicketGrantingTicket.class))).thenReturn(tgtMock); + when(ticketRegMock.getTicket(eq(tgtMock2.getId()), eq(TicketGrantingTicket.class))).thenReturn(tgtMock2); + when(ticketRegMock.getTicket(eq(stMock.getId()), eq(ServiceTicket.class))).thenReturn(stMock); + when(ticketRegMock.getTicket(eq(stMock2.getId()), eq(ServiceTicket.class))).thenReturn(stMock2); + when(ticketRegMock.getTickets()).thenReturn(Arrays.asList(tgtMock, tgtMock2, stMock, stMock2)); + + //Mock ServicesManager + final RegisteredService mockRegSvc1 = createMockRegisteredService(service1.getId(), true, getServiceProxyPolicy(false)); + final RegisteredService mockRegSvc2 = createMockRegisteredService("test", false, getServiceProxyPolicy(true)); + final RegisteredService mockRegSvc3 = createMockRegisteredService(service2.getId(), true, getServiceProxyPolicy(true)); + + final ServicesManager smMock = mock(ServicesManager.class); + when(smMock.findServiceBy(argThat(new VerifyServiceByIdMatcher(service1.getId())))).thenReturn(mockRegSvc1); + when(smMock.findServiceBy(argThat(new VerifyServiceByIdMatcher("test")))).thenReturn(mockRegSvc2); + when(smMock.findServiceBy(argThat(new VerifyServiceByIdMatcher(service2.getId())))).thenReturn(mockRegSvc3); + + final Map ticketIdGenForServiceMock = mock(Map.class); + when(ticketIdGenForServiceMock.containsKey(any())).thenReturn(true); + when(ticketIdGenForServiceMock.get(any())).thenReturn(new DefaultUniqueTicketIdGenerator()); + + this.cas = new CentralAuthenticationServiceImpl(ticketRegMock, null, mock(AuthenticationManager.class), + mock(UniqueTicketIdGenerator.class), ticketIdGenForServiceMock, mock(ExpirationPolicy.class), + mock(ExpirationPolicy.class), smMock, mock(LogoutManager.class)); + } + + @Test(expected=InvalidTicketException.class) + public void verifyNonExistentServiceWhenDelegatingTicketGrantingTicket() throws Exception { + this.cas.delegateTicketGrantingTicket("bad-st", TestUtils.getCredentialsWithSameUsernameAndPassword()); + } + + @Test(expected=UnauthorizedServiceException.class) + public void verifyInvalidServiceWhenDelegatingTicketGrantingTicket() throws Exception { + this.cas.delegateTicketGrantingTicket(ST_ID, TestUtils.getCredentialsWithSameUsernameAndPassword()); + } + + @Test(expected=UnauthorizedProxyingException.class) + public void disallowVendingServiceTicketsWhenServiceIsNotAllowedToProxyCAS1019() throws TicketException { + this.cas.grantServiceTicket(TGT_ID, TestUtils.getService(SVC1_ID)); + } + + @Test(expected=IllegalArgumentException.class) + public void getTicketGrantingTicketIfTicketIdIsNull() throws InvalidTicketException { + this.cas.getTicket(null, TicketGrantingTicket.class); + } + + @Test(expected=InvalidTicketException.class) + public void getTicketGrantingTicketIfTicketIdIsMissing() throws InvalidTicketException { + this.cas.getTicket("TGT-9000", TicketGrantingTicket.class); + } + + @Test + public void getTicketsWithNoPredicate() { + final Collection c = this.cas.getTickets(TruePredicate.INSTANCE); + assertEquals(c.size(), this.ticketRegMock.getTickets().size()); + } + + @Test + public void verifyChainedAuthenticationsOnValidation() throws TicketException { + final Service svc = TestUtils.getService(SVC2_ID); + final ServiceTicket st = this.cas.grantServiceTicket(TGT2_ID, svc); + assertNotNull(st); + + final Assertion assertion = this.cas.validateServiceTicket(st.getId(), svc); + assertNotNull(assertion); + + assertEquals(assertion.getService(), svc); + assertEquals(assertion.getPrimaryAuthentication().getPrincipal().getId(), PRINCIPAL); + assertTrue(assertion.getChainedAuthentications().size() == 2); + for (int i = 0; i < assertion.getChainedAuthentications().size(); i++) { + final Authentication auth = assertion.getChainedAuthentications().get(i); + assertEquals(auth, authentication); + } + } + + private TicketGrantingTicket createRootTicketGrantingTicket() { + final TicketGrantingTicket tgtRootMock = mock(TicketGrantingTicket.class); + when(tgtRootMock.isExpired()).thenReturn(false); + when(tgtRootMock.getAuthentication()).thenReturn(this.authentication); + return tgtRootMock; + } + + private TicketGrantingTicket createMockTicketGrantingTicket(final String id, + final ServiceTicket svcTicket, final boolean isExpired, + final TicketGrantingTicket root, final List chainedAuthnList) { + final TicketGrantingTicket tgtMock = mock(TicketGrantingTicket.class); + when(tgtMock.isExpired()).thenReturn(isExpired); + when(tgtMock.getId()).thenReturn(id); + + final String svcId = svcTicket.getService().getId(); + when(tgtMock.grantServiceTicket(anyString(), argThat(new VerifyServiceByIdMatcher(svcId)), + any(ExpirationPolicy.class), anyBoolean())).thenReturn(svcTicket); + when(tgtMock.getRoot()).thenReturn(root); + when(tgtMock.getChainedAuthentications()).thenReturn(chainedAuthnList); + when(svcTicket.getGrantingTicket()).thenReturn(tgtMock); + + return tgtMock; + } + + private ServiceTicket createMockServiceTicket(final String id, final Service svc) { + final ServiceTicket stMock = mock(ServiceTicket.class); + when(stMock.getService()).thenReturn(svc); + when(stMock.getId()).thenReturn(id); + when(stMock.isValidFor(svc)).thenReturn(true); + return stMock; + } + + private RegisteredServiceProxyPolicy getServiceProxyPolicy(final boolean canProxy) { + if (!canProxy) { + return new RefuseRegisteredServiceProxyPolicy(); + } + + return new RegexMatchingRegisteredServiceProxyPolicy(".*"); + } + + private RegisteredService createMockRegisteredService(final String svcId, + final boolean enabled, final RegisteredServiceProxyPolicy proxy) { + final RegisteredService mockRegSvc = mock(RegisteredService.class); + when(mockRegSvc.getServiceId()).thenReturn(svcId); + when(mockRegSvc.getProxyPolicy()).thenReturn(proxy); + when(mockRegSvc.getName()).thenReturn(svcId); + when(mockRegSvc.matches(argThat(new VerifyServiceByIdMatcher(svcId)))).thenReturn(true); + when(mockRegSvc.getAttributeReleasePolicy()).thenReturn(new ReturnAllAttributeReleasePolicy()); + when(mockRegSvc.getUsernameAttributeProvider()).thenReturn(new DefaultRegisteredServiceUsernameProvider()); + when(mockRegSvc.getAccessStrategy()).thenReturn(new DefaultRegisteredServiceAccessStrategy(enabled, true)); + return mockRegSvc; + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/MockExpireUpdateTicketLogoutManager.java b/cas-server-core/src/test/java/org/jasig/cas/MockExpireUpdateTicketLogoutManager.java new file mode 100644 index 000000000000..de8943d831a1 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/MockExpireUpdateTicketLogoutManager.java @@ -0,0 +1,52 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas; + +import java.util.List; + +import org.jasig.cas.logout.LogoutManager; +import org.jasig.cas.logout.LogoutRequest; +import org.jasig.cas.ticket.TicketGrantingTicket; + +/** + * This logout manager only marks the ticket as expired and update it in the {@link MockOnlyOneTicketRegistry}. + * + * @author Jerome Leleu + * @since 4.0.0 + */ +public final class MockExpireUpdateTicketLogoutManager implements LogoutManager { + + private final MockOnlyOneTicketRegistry registry; + + public MockExpireUpdateTicketLogoutManager(final MockOnlyOneTicketRegistry ticketRegistry) { + this.registry = ticketRegistry; + } + + @Override + public List performLogout(final TicketGrantingTicket ticket) { + ticket.markTicketExpired(); + registry.updateTicket(ticket); + return null; + } + + @Override + public String createFrontChannelLogoutMessage(final LogoutRequest logoutRequest) { + throw new UnsupportedOperationException("Not implemented"); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/MockOnlyOneTicketRegistry.java b/cas-server-core/src/test/java/org/jasig/cas/MockOnlyOneTicketRegistry.java new file mode 100644 index 000000000000..2a8d5b4ec807 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/MockOnlyOneTicketRegistry.java @@ -0,0 +1,70 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas; + +import java.util.Collection; + +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.registry.TicketRegistry; + +/** + * This ticket registry only stores one ticket at the same time and offers the ability to update a ticket. + * + * @author Jerome Leleu + * @since 4.0.0 + */ +public final class MockOnlyOneTicketRegistry implements TicketRegistry { + + private Ticket ticket; + + @Override + public void addTicket(final Ticket ticket) { + this.ticket = ticket; + } + + public void updateTicket(final Ticket ticket) { + // ticket must exist + if (this.ticket == null) { + throw new IllegalArgumentException("No ticket to update"); + } + addTicket(ticket); + } + + @SuppressWarnings("unchecked") + @Override + public T getTicket(final String ticketId, final Class clazz) { + return (T) this.ticket; + } + + @Override + public Ticket getTicket(final String ticketId) { + return this.ticket; + } + + @Override + public boolean deleteTicket(final String ticketId) { + this.ticket = null; + return false; + } + + @Override + public Collection getTickets() { + throw new UnsupportedOperationException("Not implemented"); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/MultifactorAuthenticationTests.java b/cas-server-core/src/test/java/org/jasig/cas/MultifactorAuthenticationTests.java new file mode 100644 index 000000000000..44ffb098d217 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/MultifactorAuthenticationTests.java @@ -0,0 +1,140 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas; + +import org.jasig.cas.authentication.MixedPrincipalException; +import org.jasig.cas.authentication.OneTimePasswordCredential; +import org.jasig.cas.authentication.SuccessfulHandlerMetaDataPopulator; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.UnsatisfiedAuthenticationPolicyException; +import org.jasig.cas.validation.Assertion; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.*; + +/** + * High-level MFA functionality tests that leverage registered service metadata + * ala {@link org.jasig.cas.authentication.RequiredHandlerAuthenticationPolicyFactory} to drive + * authentication policy. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "/mfa-test-context.xml" }) +public class MultifactorAuthenticationTests { + + @Autowired + private CentralAuthenticationService cas; + + @Test + public void verifyAllowsAccessToNormalSecurityServiceWithPassword() throws Exception { + final TicketGrantingTicket tgt = cas.createTicketGrantingTicket(newUserPassCredentials("alice", "alice")); + assertNotNull(tgt); + final ServiceTicket st = cas.grantServiceTicket(tgt.getId(), newService("https://example.com/normal/")); + assertNotNull(st); + } + + @Test + public void verifyAllowsAccessToNormalSecurityServiceWithOTP() throws Exception { + final TicketGrantingTicket tgt = cas.createTicketGrantingTicket(new OneTimePasswordCredential("alice", "31415")); + assertNotNull(tgt); + final ServiceTicket st = cas.grantServiceTicket(tgt.getId(), newService("https://example.com/normal/")); + assertNotNull(st); + } + + @Test(expected = UnsatisfiedAuthenticationPolicyException.class) + public void verifyDeniesAccessToHighSecurityServiceWithPassword() throws Exception { + final TicketGrantingTicket tgt = cas.createTicketGrantingTicket(newUserPassCredentials("alice", "alice")); + assertNotNull(tgt); + cas.grantServiceTicket(tgt.getId(), newService("https://example.com/high/")); + } + + @Test(expected = UnsatisfiedAuthenticationPolicyException.class) + public void verifyDeniesAccessToHighSecurityServiceWithOTP() throws Exception { + final TicketGrantingTicket tgt = cas.createTicketGrantingTicket(new OneTimePasswordCredential("alice", "31415")); + assertNotNull(tgt); + final ServiceTicket st = cas.grantServiceTicket(tgt.getId(), newService("https://example.com/high/")); + assertNotNull(st); + } + + @Test + public void verifyAllowsAccessToHighSecurityServiceWithPasswordAndOTP() throws Exception { + final TicketGrantingTicket tgt = cas.createTicketGrantingTicket( + newUserPassCredentials("alice", "alice"), + new OneTimePasswordCredential("alice", "31415")); + assertNotNull(tgt); + final ServiceTicket st = cas.grantServiceTicket(tgt.getId(), newService("https://example.com/high/")); + assertNotNull(st); + } + + @Test + public void verifyAllowsAccessToHighSecurityServiceWithPasswordAndOTPViaRenew() throws Exception { + // Note the original credential used to start SSO session does not satisfy security policy + final TicketGrantingTicket tgt = cas.createTicketGrantingTicket(newUserPassCredentials("alice", "alice")); + assertNotNull(tgt); + final Service service = newService("https://example.com/high/"); + final ServiceTicket st = cas.grantServiceTicket( + tgt.getId(), + service, + newUserPassCredentials("alice", "alice"), + new OneTimePasswordCredential("alice", "31415")); + assertNotNull(st); + // Confirm the authentication in the assertion is the one that satisfies security policy + final Assertion assertion = cas.validateServiceTicket(st.getId(), service); + assertEquals(2, assertion.getPrimaryAuthentication().getSuccesses().size()); + assertTrue(assertion.getPrimaryAuthentication().getSuccesses().containsKey("passwordHandler")); + assertTrue(assertion.getPrimaryAuthentication().getSuccesses().containsKey("oneTimePasswordHandler")); + assertTrue(assertion.getPrimaryAuthentication().getAttributes().containsKey( + SuccessfulHandlerMetaDataPopulator.SUCCESSFUL_AUTHENTICATION_HANDLERS)); + } + + + @Test(expected = MixedPrincipalException.class) + public void verifyThrowsMixedPrincipalExceptionOnRenewWithDifferentPrincipal() throws Exception { + // Note the original credential used to start SSO session does not satisfy security policy + final TicketGrantingTicket tgt = cas.createTicketGrantingTicket(newUserPassCredentials("alice", "alice")); + assertNotNull(tgt); + final Service service = newService("https://example.com/high/"); + cas.grantServiceTicket( + tgt.getId(), + service, + newUserPassCredentials("bob", "bob"), + new OneTimePasswordCredential("bob", "62831")); + } + + private static UsernamePasswordCredential newUserPassCredentials(final String user, final String pass) { + final UsernamePasswordCredential userpass = new UsernamePasswordCredential(); + userpass.setUsername(user); + userpass.setPassword(pass); + return userpass; + } + + private static Service newService(final String id) { + return new SimpleWebApplicationServiceImpl(id); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/TestOneTimePasswordAuthenticationHandler.java b/cas-server-core/src/test/java/org/jasig/cas/TestOneTimePasswordAuthenticationHandler.java new file mode 100644 index 000000000000..6dedec5db6a7 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/TestOneTimePasswordAuthenticationHandler.java @@ -0,0 +1,89 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas; + +import org.jasig.cas.authentication.AuthenticationHandler; +import org.jasig.cas.authentication.BasicCredentialMetaData; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.OneTimePasswordCredential; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.springframework.util.StringUtils; + +import javax.security.auth.login.FailedLoginException; +import javax.validation.constraints.NotNull; +import java.security.GeneralSecurityException; +import java.util.Map; + +/** + * Test one-time password authentication handler that supports {@link MultifactorAuthenticationTests}. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class TestOneTimePasswordAuthenticationHandler implements AuthenticationHandler { + + @NotNull + private final Map credentialMap; + + /** Handler name. */ + private String name; + + + /** + * Creates a new instance with a map that defines the one-time passwords that can be authenticated. + * + * @param credentialMap Non-null map of one-time password identifiers to password values. + */ + public TestOneTimePasswordAuthenticationHandler(final Map credentialMap) { + this.credentialMap = credentialMap; + } + + @Override + public HandlerResult authenticate(final Credential credential) + throws GeneralSecurityException, PreventedException { + final OneTimePasswordCredential otp = (OneTimePasswordCredential) credential; + final String valueOnRecord = credentialMap.get(otp.getId()); + if (otp.getPassword().equals(credentialMap.get(otp.getId()))) { + return new DefaultHandlerResult(this, new BasicCredentialMetaData(otp), + new DefaultPrincipalFactory().createPrincipal(otp.getId())); + } + throw new FailedLoginException(); + } + + @Override + public boolean supports(final Credential credential) { + return credential instanceof OneTimePasswordCredential; + } + + @Override + public String getName() { + if (StringUtils.hasText(this.name)) { + return this.name; + } else { + return getClass().getSimpleName(); + } + } + + public void setName(final String name) { + this.name = name; + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/TestUtils.java b/cas-server-core/src/test/java/org/jasig/cas/TestUtils.java new file mode 100644 index 000000000000..f009a2072487 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/TestUtils.java @@ -0,0 +1,230 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.DefaultAuthenticationBuilder; +import org.jasig.cas.authentication.AuthenticationHandler; +import org.jasig.cas.authentication.BasicCredentialMetaData; +import org.jasig.cas.authentication.CredentialMetaData; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.HttpBasedServiceCredential; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.services.AbstractRegisteredService; +import org.jasig.cas.services.RegexMatchingRegisteredServiceProxyPolicy; +import org.jasig.cas.services.RegisteredServiceImpl; +import org.jasig.cas.validation.Assertion; +import org.jasig.cas.validation.ImmutableAssertion; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.validation.BindException; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.test.MockRequestContext; + +import java.net.MalformedURLException; +import java.net.URL; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * @author Scott Battaglia + * @since 3.0.0.2 + */ +public final class TestUtils { + + public static final String CONST_USERNAME = "test"; + + public static final String CONST_TEST_URL = "https://test.com"; + + public static final String CONST_EXCEPTION_EXPECTED = "Exception expected."; + + public static final String CONST_EXCEPTION_NON_EXPECTED = "Exception not expected."; + + public static final String CONST_GOOD_URL = "https://github.com/"; + + private static final String CONST_PASSWORD = "test1"; + + private static final String CONST_CREDENTIALS = "credentials"; + + private static final String CONST_WEBFLOW_BIND_EXCEPTION = + "org.springframework.validation.BindException.credentials"; + + private static final String[] CONST_NO_PRINCIPALS = new String[0]; + + private TestUtils() { + // do not instantiate + } + + public static UsernamePasswordCredential getCredentialsWithSameUsernameAndPassword() { + return getCredentialsWithSameUsernameAndPassword(CONST_USERNAME); + } + + public static UsernamePasswordCredential getCredentialsWithSameUsernameAndPassword( + final String username) { + return getCredentialsWithDifferentUsernameAndPassword(username, + username); + } + + public static UsernamePasswordCredential getCredentialsWithDifferentUsernameAndPassword() { + return getCredentialsWithDifferentUsernameAndPassword(CONST_USERNAME, + CONST_PASSWORD); + } + + public static UsernamePasswordCredential getCredentialsWithDifferentUsernameAndPassword( + final String username, final String password) { + // noinspection LocalVariableOfConcreteClass + final UsernamePasswordCredential usernamePasswordCredentials = new UsernamePasswordCredential(); + usernamePasswordCredentials.setUsername(username); + usernamePasswordCredentials.setPassword(password); + + return usernamePasswordCredentials; + } + + public static HttpBasedServiceCredential getHttpBasedServiceCredentials() { + return getHttpBasedServiceCredentials(CONST_GOOD_URL); + } + + public static HttpBasedServiceCredential getHttpBasedServiceCredentials( + final String url) { + try { + return new HttpBasedServiceCredential(new URL(url), TestUtils.getRegisteredService(url)); + } catch (final MalformedURLException e) { + throw new IllegalArgumentException(); + } + } + + public static Principal getPrincipal() { + return getPrincipal(CONST_USERNAME); + } + + public static Principal getPrincipal(final String name) { + return getPrincipal(name, Collections.EMPTY_MAP); + } + + public static Principal getPrincipal(final String name, final Map attributes) { + return new DefaultPrincipalFactory().createPrincipal(name, attributes); + } + + public static AbstractRegisteredService getRegisteredService(final String id) { + final RegisteredServiceImpl s = new RegisteredServiceImpl(); + s.setServiceId(id); + s.setEvaluationOrder(1); + s.setName("Test registered service"); + s.setDescription("Registered service description"); + s.setProxyPolicy(new RegexMatchingRegisteredServiceProxyPolicy("^https?://.+")); + s.setId(new SecureRandom().nextInt(32)); + return s; + } + + public static Service getService() { + return getService(CONST_TEST_URL); + } + + public static Service getService(final String name) { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", name); + return SimpleWebApplicationServiceImpl.createServiceFrom(request); + } + + public static Authentication getAuthentication() { + return getAuthentication(CONST_USERNAME); + } + + public static Authentication getAuthentication(final String name) { + return getAuthentication(getPrincipal(name)); + } + + public static Authentication getAuthentication(final Principal principal) { + return getAuthentication(principal, Collections.emptyMap()); + } + + public static Authentication getAuthentication(final Principal principal, final Map attributes) { + final AuthenticationHandler handler = new SimpleTestUsernamePasswordAuthenticationHandler(); + final CredentialMetaData meta = new BasicCredentialMetaData(new UsernamePasswordCredential()); + return new DefaultAuthenticationBuilder(principal) + .addCredential(meta) + .addSuccess("testHandler", new DefaultHandlerResult(handler, meta)) + .setAttributes(attributes) + .build(); + } + + public static Authentication getAuthenticationWithService() { + return getAuthentication(getService()); + } + + public static Assertion getAssertion(final boolean fromNewLogin) { + return getAssertion(fromNewLogin, CONST_NO_PRINCIPALS); + } + + public static Assertion getAssertion(final boolean fromNewLogin, + final String[] extraPrincipals) { + final List list = new ArrayList<>(); + list.add(TestUtils.getAuthentication()); + + for (int i = 0; i < extraPrincipals.length; i++) { + list.add(TestUtils.getAuthentication(extraPrincipals[i])); + } + return new ImmutableAssertion(TestUtils.getAuthentication(), list, TestUtils.getService(), fromNewLogin); + } + + public static MockRequestContext getContext() { + return getContext(new MockHttpServletRequest()); + } + + public static MockRequestContext getContext( + final MockHttpServletRequest request) { + return getContext(request, new MockHttpServletResponse()); + } + + public static MockRequestContext getContext( + final MockHttpServletRequest request, + final MockHttpServletResponse response) { + final MockRequestContext context = new MockRequestContext(); + context.setExternalContext(new ServletExternalContext(new MockServletContext(), request, response)); + return context; + } + + public static MockRequestContext getContextWithCredentials( + final MockHttpServletRequest request) { + return getContextWithCredentials(request, new MockHttpServletResponse()); + } + + public static MockRequestContext getContextWithCredentials( + final MockHttpServletRequest request, + final MockHttpServletResponse response) { + final MockRequestContext context = getContext(request, response); + context.getRequestScope().put(CONST_CREDENTIALS, TestUtils + .getCredentialsWithSameUsernameAndPassword()); + context.getRequestScope().put(CONST_WEBFLOW_BIND_EXCEPTION, + new BindException(TestUtils + .getCredentialsWithSameUsernameAndPassword(), + CONST_CREDENTIALS)); + + return context; + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/audit/spi/TicketOrCredentialPrincipalResolverTests.java b/cas-server-core/src/test/java/org/jasig/cas/audit/spi/TicketOrCredentialPrincipalResolverTests.java new file mode 100644 index 000000000000..4ce1b91a29b7 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/audit/spi/TicketOrCredentialPrincipalResolverTests.java @@ -0,0 +1,120 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.audit.spi; + +import org.jasig.inspektr.common.spi.PrincipalResolver; +import org.aspectj.lang.JoinPoint; +import org.jasig.cas.AbstractCentralAuthenticationServiceTest; +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.junit.Test; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests for {@link TicketOrCredentialPrincipalResolver} + * @author Misagh Moayyed + * @since 4.0.0 + */ +public class TicketOrCredentialPrincipalResolverTests extends AbstractCentralAuthenticationServiceTest { + + @Test + public void verifyResolverByUnknownUser() { + final TicketOrCredentialPrincipalResolver res = + new TicketOrCredentialPrincipalResolver(getCentralAuthenticationService()); + assertEquals(res.resolve(), PrincipalResolver.UNKNOWN_USER); + } + + @Test + public void verifyResolverCredential() { + final TicketOrCredentialPrincipalResolver res = + new TicketOrCredentialPrincipalResolver(getCentralAuthenticationService()); + final JoinPoint jp = mock(JoinPoint.class); + + final Credential c = TestUtils.getCredentialsWithSameUsernameAndPassword(); + when(jp.getArgs()).thenReturn(new Object[] {c}); + + final String result = res.resolveFrom(jp, null); + assertNotNull(result); + assertEquals(result, c.toString()); + } + + @Test + public void verifyResolverServiceTicket() throws Exception { + final Credential c = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final TicketGrantingTicket ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket(c); + final ServiceTicket st = getCentralAuthenticationService().grantServiceTicket(ticketId.getId(), + TestUtils.getService()); + + final TicketOrCredentialPrincipalResolver res = + new TicketOrCredentialPrincipalResolver(getCentralAuthenticationService()); + final JoinPoint jp = mock(JoinPoint.class); + + when(jp.getArgs()).thenReturn(new Object[] {st.getId()}); + + final String result = res.resolveFrom(jp, null); + assertNotNull(result); + assertEquals(result, c.getId()); + } + + @Test + public void verifyResolverTicketGrantingTicket() throws Exception { + final Credential c = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final TicketGrantingTicket ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket(c); + + final TicketOrCredentialPrincipalResolver res = + new TicketOrCredentialPrincipalResolver(getCentralAuthenticationService()); + final JoinPoint jp = mock(JoinPoint.class); + + when(jp.getArgs()).thenReturn(new Object[] {ticketId.getId()}); + + final String result = res.resolveFrom(jp, null); + assertNotNull(result); + assertEquals(result, c.getId()); + } + + @Test + public void verifyResolverSecurityContext() throws Exception { + final UserDetails ud = mock(UserDetails.class); + when(ud.getUsername()).thenReturn("pid"); + final Authentication authn = mock(Authentication.class); + when(authn.getPrincipal()).thenReturn(ud); + final SecurityContext securityContext = mock(SecurityContext.class); + when(securityContext.getAuthentication()).thenReturn(authn); + SecurityContextHolder.setContext(securityContext); + + final TicketOrCredentialPrincipalResolver res = + new TicketOrCredentialPrincipalResolver(getCentralAuthenticationService()); + final JoinPoint jp = mock(JoinPoint.class); + when(jp.getArgs()).thenReturn(new Object[]{ud}); + + final String result = res.resolveFrom(jp, null); + assertNotNull(result); + assertEquals(result, ud.getUsername()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/AcceptUsersAuthenticationHandlerTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/AcceptUsersAuthenticationHandlerTests.java new file mode 100644 index 000000000000..6dcad8e12300 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/AcceptUsersAuthenticationHandlerTests.java @@ -0,0 +1,139 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.TestUtils; +import org.junit.Test; + +import javax.security.auth.login.AccountNotFoundException; +import javax.security.auth.login.FailedLoginException; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.GeneralSecurityException; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class AcceptUsersAuthenticationHandlerTests { + + private final Map users; + + private final AcceptUsersAuthenticationHandler authenticationHandler; + + public AcceptUsersAuthenticationHandlerTests() throws Exception { + this.users = new HashMap<>(); + + this.users.put("scott", "rutgers"); + this.users.put("dima", "javarules"); + this.users.put("bill", "thisisAwesoME"); + this.users.put("brian", "t�st"); + + this.authenticationHandler = new AcceptUsersAuthenticationHandler(); + + this.authenticationHandler.setUsers(this.users); + } + + @Test + public void verifySupportsSpecialCharacters() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + c.setUsername("brian"); + c.setPassword("t�st"); + assertEquals("brian", this.authenticationHandler.authenticate(c).getPrincipal().getId()); + + } + + @Test + public void verifySupportsProperUserCredentials() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + c.setUsername("scott"); + c.setPassword("rutgers"); + assertTrue(this.authenticationHandler.supports(c)); + } + + @Test + public void verifyDoesntSupportBadUserCredentials() { + try { + assertFalse(this.authenticationHandler + .supports(new HttpBasedServiceCredential(new URL( + "http://www.rutgers.edu"), TestUtils.getRegisteredService("https://some.app.edu")))); + } catch (final MalformedURLException e) { + fail("Could not resolve URL."); + } + } + + @Test + public void verifyAuthenticatesUserInMap() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + c.setUsername("scott"); + c.setPassword("rutgers"); + + try { + assertEquals("scott", this.authenticationHandler.authenticate(c).getPrincipal().getId()); + } catch (final GeneralSecurityException e) { + fail("AuthenticationException caught but it should not have been thrown."); + } + } + + @Test(expected = AccountNotFoundException.class) + public void verifyFailsUserNotInMap() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + c.setUsername("fds"); + c.setPassword("rutgers"); + + this.authenticationHandler.authenticate(c); + } + + @Test(expected = AccountNotFoundException.class) + public void verifyFailsNullUserName() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + c.setUsername(null); + c.setPassword("user"); + + this.authenticationHandler.authenticate(c); + } + + @Test(expected = AccountNotFoundException.class) + public void verifyFailsNullUserNameAndPassword() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + c.setUsername(null); + c.setPassword(null); + + this.authenticationHandler.authenticate(c); + } + + @Test(expected = FailedLoginException.class) + public void verifyFailsNullPassword() throws Exception{ + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + c.setUsername("scott"); + c.setPassword(null); + + this.authenticationHandler.authenticate(c); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/CacheCredentialsMetaDataPopulatorTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/CacheCredentialsMetaDataPopulatorTests.java new file mode 100644 index 000000000000..090338b70eaa --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/CacheCredentialsMetaDataPopulatorTests.java @@ -0,0 +1,49 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.authentication; + +import org.jasig.cas.TestUtils; +import org.junit.Test; + + +import static org.junit.Assert.*; + +/** + * Tests for {@link org.jasig.cas.authentication.CacheCredentialsMetaDataPopulator}. + * @author Misagh Moayyed + * @since 4.1 + */ +public class CacheCredentialsMetaDataPopulatorTests { + + @Test + public void verifyPasswordAsAuthenticationAttribute() { + final CacheCredentialsMetaDataPopulator populator = new CacheCredentialsMetaDataPopulator(); + + final UsernamePasswordCredential c = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final AuthenticationBuilder builder = DefaultAuthenticationBuilder.newInstance(TestUtils.getAuthentication()); + populator.populateAttributes(builder, c); + final Authentication authn = builder.build(); + assertTrue(authn.getAttributes().containsKey(UsernamePasswordCredential.AUTHENTICATION_ATTRIBUTE_PASSWORD)); + assertTrue(authn.getAttributes().get(UsernamePasswordCredential.AUTHENTICATION_ATTRIBUTE_PASSWORD) + .equals(c.getPassword())); + } + + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/FileTrustStoreSslSocketFactoryTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/FileTrustStoreSslSocketFactoryTests.java new file mode 100644 index 000000000000..444aa26082a9 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/FileTrustStoreSslSocketFactoryTests.java @@ -0,0 +1,88 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.util.http.HttpClient; +import org.jasig.cas.util.http.SimpleHttpClientFactoryBean; +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; + +import java.io.File; + +import static org.junit.Assert.*; + +/** + * Tests for the FileTrustStoreSslSocketFactory class, checking for self-signed + * and missing certificates via a local truststore. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class FileTrustStoreSslSocketFactoryTests { + + @Test + public void verifyTrustStoreLoadingSuccessfullyWithCertAvailable() throws Exception { + final ClassPathResource resource = new ClassPathResource("truststore.jks"); + final FileTrustStoreSslSocketFactory factory = new FileTrustStoreSslSocketFactory(resource.getFile(), "changeit"); + final SimpleHttpClientFactoryBean clientFactory = new SimpleHttpClientFactoryBean(); + clientFactory.setSslSocketFactory(factory); + final HttpClient client = clientFactory.getObject(); + assertTrue(client.isValidEndPoint("https://www.cacert.org")); + } + + @Test + public void verifyTrustStoreLoadingSuccessfullyWithCertAvailable2() throws Exception { + final ClassPathResource resource = new ClassPathResource("truststore.jks"); + final FileTrustStoreSslSocketFactory factory = new FileTrustStoreSslSocketFactory(resource.getFile(), "changeit"); + final SimpleHttpClientFactoryBean clientFactory = new SimpleHttpClientFactoryBean(); + clientFactory.setSslSocketFactory(factory); + final HttpClient client = clientFactory.getObject(); + assertTrue(client.isValidEndPoint("https://test.scaldingspoon.org/idp/shibboleth")); + } + + + @Test(expected = RuntimeException.class) + public void verifyTrustStoreNotFound() throws Exception { + new FileTrustStoreSslSocketFactory(new File("test.jks"), "changeit"); + } + + @Test(expected = RuntimeException.class) + public void verifyTrustStoreBadPassword() throws Exception { + final ClassPathResource resource = new ClassPathResource("truststore.jks"); + new FileTrustStoreSslSocketFactory(resource.getFile(), "invalid"); + } + + @Test + public void verifyTrustStoreLoadingSuccessfullyForValidEndpointWithNoCert() throws Exception { + final ClassPathResource resource = new ClassPathResource("truststore.jks"); + final FileTrustStoreSslSocketFactory factory = new FileTrustStoreSslSocketFactory(resource.getFile(), "changeit"); + final SimpleHttpClientFactoryBean clientFactory = new SimpleHttpClientFactoryBean(); + clientFactory.setSslSocketFactory(factory); + final HttpClient client = clientFactory.getObject(); + assertTrue(client.isValidEndPoint("https://www.google.com")); + } + @Test + public void verifyTrustStoreLoadingSuccessfullyWihInsecureEndpoint() throws Exception { + final ClassPathResource resource = new ClassPathResource("truststore.jks"); + final FileTrustStoreSslSocketFactory factory = new FileTrustStoreSslSocketFactory(resource.getFile(), "changeit"); + final SimpleHttpClientFactoryBean clientFactory = new SimpleHttpClientFactoryBean(); + clientFactory.setSslSocketFactory(factory); + final HttpClient client = clientFactory.getObject(); + assertTrue(client.isValidEndPoint("http://wikipedia.org")); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/HttpBasedServiceCredentialTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/HttpBasedServiceCredentialTests.java new file mode 100644 index 000000000000..bab9dd322a21 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/HttpBasedServiceCredentialTests.java @@ -0,0 +1,69 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.TestUtils; +import org.junit.Test; + +import java.net.URL; + +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public final class HttpBasedServiceCredentialTests { + + @Test + public void verifyProperUrl() { + assertEquals(TestUtils.CONST_GOOD_URL, TestUtils.getHttpBasedServiceCredentials().getCallbackUrl() + .toExternalForm()); + } + + @Test + public void verifyEqualsWithNull() throws Exception { + final HttpBasedServiceCredential c = new HttpBasedServiceCredential(new URL("http://www.cnn.com"), + TestUtils.getRegisteredService("https://some.app.edu")); + + assertNotEquals(c, null); + } + + @Test + public void verifyEqualsWithFalse() throws Exception { + final HttpBasedServiceCredential c = new HttpBasedServiceCredential(new URL("http://www.cnn.com"), + TestUtils.getRegisteredService("https://some.app.edu")); + final HttpBasedServiceCredential c2 = new HttpBasedServiceCredential(new URL("http://www.msn.com"), + TestUtils.getRegisteredService("https://some.app.edu")); + + assertFalse(c.equals(c2)); + assertFalse(c.equals(new Object())); + } + + @Test + public void verifyEqualsWithTrue() throws Exception { + final HttpBasedServiceCredential c = new HttpBasedServiceCredential(new URL("http://www.cnn.com"), + TestUtils.getRegisteredService("https://some.app.edu")); + final HttpBasedServiceCredential c2 = new HttpBasedServiceCredential(new URL("http://www.cnn.com"), + TestUtils.getRegisteredService("https://some.app.edu")); + + assertTrue(c.equals(c2)); + assertTrue(c2.equals(c)); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/ImmutableAuthenticationTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/ImmutableAuthenticationTests.java new file mode 100644 index 000000000000..63ce2213de36 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/ImmutableAuthenticationTests.java @@ -0,0 +1,90 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.joda.time.DateTime; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.security.auth.login.FailedLoginException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.0.0 + */ +public class ImmutableAuthenticationTests { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Test + public void verifyImmutable() { + final AuthenticationHandler authenticationHandler = new SimpleTestUsernamePasswordAuthenticationHandler(); + final CredentialMetaData credential1 = new BasicCredentialMetaData(new UsernamePasswordCredential()); + final CredentialMetaData credential2 = new BasicCredentialMetaData(new UsernamePasswordCredential()); + final List credentials = new ArrayList<>(); + credentials.add(credential1); + credentials.add(credential2); + final Map attributes = new HashMap<>(); + attributes.put("authenticationMethod", "password"); + final Map successes = new HashMap<>(); + successes.put("handler1", new DefaultHandlerResult(authenticationHandler, credential1)); + final Map> failures = new HashMap<>(); + failures.put("handler2", FailedLoginException.class); + final ImmutableAuthentication auth = new ImmutableAuthentication( + new DateTime(), + credentials, + new DefaultPrincipalFactory().createPrincipal("test"), + attributes, + successes, + failures); + try { + auth.getAuthenticationDate().setTime(100); + fail("Should have failed"); + } catch (final RuntimeException e) { + logger.debug("Setting authenticate date/time failed correctly"); + } + try { + auth.getCredentials().add(new BasicCredentialMetaData(new UsernamePasswordCredential())); + fail("Should have failed"); + } catch (final RuntimeException e) { + logger.debug("Adding authentication credential metadata failed correctly"); + } + try { + auth.getSuccesses().put("test", new DefaultHandlerResult(authenticationHandler, credential1)); + fail("Should have failed"); + } catch (final RuntimeException e) { + logger.debug("Adding authentication success event failed correctly"); + } + try { + auth.getFailures().put("test", FailedLoginException.class); + fail("Should have failed"); + } catch (final RuntimeException e) { + logger.debug("Adding authentication failure event failed correctly"); + } + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/PolicyBasedAuthenticationManagerTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/PolicyBasedAuthenticationManagerTests.java new file mode 100644 index 000000000000..b1c52a016fb1 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/PolicyBasedAuthenticationManagerTests.java @@ -0,0 +1,173 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.jasig.cas.authentication.principal.Principal; +import org.junit.Test; + +import javax.security.auth.login.FailedLoginException; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Unit test for {@link PolicyBasedAuthenticationManager}. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class PolicyBasedAuthenticationManagerTests { + + @Test + public void verifyAuthenticateAnySuccess() throws Exception { + final PolicyBasedAuthenticationManager manager = new PolicyBasedAuthenticationManager( + newMockHandler(true), + newMockHandler(false)); + final Authentication auth = manager.authenticate(mock(Credential.class), mock(Credential.class)); + assertEquals(1, auth.getSuccesses().size()); + assertEquals(0, auth.getFailures().size()); + assertEquals(2, auth.getCredentials().size()); + } + + @Test + public void verifyAuthenticateAnyButTryAllSuccess() throws Exception { + final PolicyBasedAuthenticationManager manager = new PolicyBasedAuthenticationManager( + newMockHandler(true), + newMockHandler(false)); + final AnyAuthenticationPolicy any = new AnyAuthenticationPolicy(); + any.setTryAll(true); + manager.setAuthenticationPolicy(any); + final Authentication auth = manager.authenticate(mock(Credential.class), mock(Credential.class)); + assertEquals(1, auth.getSuccesses().size()); + assertEquals(1, auth.getFailures().size()); + assertEquals(2, auth.getCredentials().size()); + } + + @Test(expected = AuthenticationException.class) + public void verifyAuthenticateAnyFailure() throws Exception { + final PolicyBasedAuthenticationManager manager = new PolicyBasedAuthenticationManager( + newMockHandler(false), + newMockHandler(false)); + manager.authenticate(mock(Credential.class), mock(Credential.class)); + fail("Should have thrown AuthenticationException"); + } + + @Test + public void verifyAuthenticateAllSuccess() throws Exception { + final PolicyBasedAuthenticationManager manager = new PolicyBasedAuthenticationManager( + newMockHandler(true), + newMockHandler(true)); + manager.setAuthenticationPolicy(new AllAuthenticationPolicy()); + final Authentication auth = manager.authenticate(mock(Credential.class), mock(Credential.class)); + assertEquals(2, auth.getSuccesses().size()); + assertEquals(0, auth.getFailures().size()); + assertEquals(2, auth.getCredentials().size()); + } + + @Test(expected = AuthenticationException.class) + public void verifyAuthenticateAllFailure() throws Exception { + final PolicyBasedAuthenticationManager manager = new PolicyBasedAuthenticationManager( + newMockHandler(false), + newMockHandler(false)); + manager.setAuthenticationPolicy(new AllAuthenticationPolicy()); + manager.authenticate(mock(Credential.class), mock(Credential.class)); + fail("Should have thrown AuthenticationException"); + } + + @Test + public void verifyAuthenticateRequiredHandlerSuccess() throws Exception { + final PolicyBasedAuthenticationManager manager = new PolicyBasedAuthenticationManager( + newMockHandler("HandlerA", true), + newMockHandler("HandlerB", false)); + manager.setAuthenticationPolicy(new RequiredHandlerAuthenticationPolicy("HandlerA")); + final Authentication auth = manager.authenticate(mock(Credential.class), mock(Credential.class)); + assertEquals(1, auth.getSuccesses().size()); + assertEquals(0, auth.getFailures().size()); + assertEquals(2, auth.getCredentials().size()); + } + + @Test(expected = AuthenticationException.class) + public void verifyAuthenticateRequiredHandlerFailure() throws Exception { + final PolicyBasedAuthenticationManager manager = new PolicyBasedAuthenticationManager( + newMockHandler("HandlerA", true), + newMockHandler("HandlerB", false)); + manager.setAuthenticationPolicy(new RequiredHandlerAuthenticationPolicy("HandlerB")); + manager.authenticate(mock(Credential.class), mock(Credential.class)); + fail("Should have thrown AuthenticationException"); + } + + @Test + public void verifyAuthenticateRequiredHandlerTryAllSuccess() throws Exception { + final PolicyBasedAuthenticationManager manager = new PolicyBasedAuthenticationManager( + newMockHandler("HandlerA", true), + newMockHandler("HandlerB", false)); + final RequiredHandlerAuthenticationPolicy policy = new RequiredHandlerAuthenticationPolicy("HandlerA"); + policy.setTryAll(true); + manager.setAuthenticationPolicy(policy); + final Authentication auth = manager.authenticate(mock(Credential.class), mock(Credential.class)); + assertEquals(1, auth.getSuccesses().size()); + assertEquals(1, auth.getFailures().size()); + assertEquals(2, auth.getCredentials().size()); + } + + /** + * Creates a new mock authentication handler that either successfully validates all credentials or fails to + * validate all credentials. + * + * @param success True to authenticate all credentials, false to fail all credentials. + * + * @return New mock authentication handler instance. + * + * @throws Exception On errors. + */ + private static AuthenticationHandler newMockHandler(final boolean success) throws Exception { + return newMockHandler("MockAuthenticationHandler" + System.nanoTime(), success); + } + + /** + * Creates a new named mock authentication handler that either successfully validates all credentials or fails to + * validate all credentials. + * + * @param name Authentication handler name. + * @param success True to authenticate all credentials, false to fail all credentials. + * + * @return New mock authentication handler instance. + * + * @throws Exception On errors. + */ + private static AuthenticationHandler newMockHandler(final String name, final boolean success) throws Exception { + final AuthenticationHandler mock = mock(AuthenticationHandler.class); + when(mock.getName()).thenReturn(name); + when(mock.supports(any(Credential.class))).thenReturn(true); + if (success) { + final Principal p = new DefaultPrincipalFactory().createPrincipal("nobody"); + + final HandlerResult result = new DefaultHandlerResult( + mock, + mock(CredentialMetaData.class), + p); + when(mock.authenticate(any(Credential.class))).thenReturn(result); + } else { + when(mock.authenticate(any(Credential.class))).thenThrow(new FailedLoginException()); + } + return mock; + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/RememberMePasswordCredentialTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/RememberMePasswordCredentialTests.java new file mode 100644 index 000000000000..611a0a567f13 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/RememberMePasswordCredentialTests.java @@ -0,0 +1,45 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * Tests for RememberMeUsernamePasswordCredential. + * + * @author Scott Battaglia + * @since 3.2.1 + * + */ +public final class RememberMePasswordCredentialTests { + + @Test + public void verifyGettersAndSetters() { + final RememberMeUsernamePasswordCredential c = new RememberMeUsernamePasswordCredential(); + c.setPassword("password"); + c.setUsername("username"); + c.setRememberMe(true); + + assertEquals("username", c.getUsername()); + assertEquals("password", c.getPassword()); + assertTrue(c.isRememberMe()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/SimpleServiceTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/SimpleServiceTests.java new file mode 100644 index 000000000000..ad9caaf9173d --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/SimpleServiceTests.java @@ -0,0 +1,52 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.TestUtils; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + + * @since 3.0.0 + */ +public class SimpleServiceTests { + + @Test + public void verifyProperId() { + assertEquals("Ids are not equal.", TestUtils.CONST_TEST_URL, TestUtils.getService().getId()); + } + + @Test + public void verifyEqualsWithNull() { + assertNotEquals("Service matches null.", TestUtils.getService(), null); + } + + @Test + public void verifyEqualsWithBadClass() { + assertFalse("Services matches String class.", TestUtils.getService().equals(new Object())); + } + + @Test + public void verifyEquals() { + assertTrue("Services are not equal.", TestUtils.getService().equals(TestUtils.getService())); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/UsernamePasswordCredentialTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/UsernamePasswordCredentialTests.java new file mode 100644 index 000000000000..036a26d933dd --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/UsernamePasswordCredentialTests.java @@ -0,0 +1,60 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.TestUtils; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class UsernamePasswordCredentialTests { + + @Test + public void verifySetGetUsername() { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + final String userName = "test"; + + c.setUsername(userName); + + assertEquals(userName, c.getUsername()); + } + + @Test + public void verifySetGetPassword() { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + final String password = "test"; + + c.setPassword(password); + + assertEquals(password, c.getPassword()); + } + + @Test + public void verifyEquals() { + assertNotEquals(TestUtils.getCredentialsWithDifferentUsernameAndPassword(), null); + assertFalse(TestUtils.getCredentialsWithDifferentUsernameAndPassword().equals( + TestUtils.getCredentialsWithSameUsernameAndPassword())); + assertTrue(TestUtils.getCredentialsWithDifferentUsernameAndPassword().equals( + TestUtils.getCredentialsWithDifferentUsernameAndPassword())); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/BadCredentialsAuthenticationExceptionTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/BadCredentialsAuthenticationExceptionTests.java new file mode 100644 index 000000000000..ed893f9338ea --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/BadCredentialsAuthenticationExceptionTests.java @@ -0,0 +1,66 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +import static org.junit.Assert.*; + +import org.junit.Test; + + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public final class BadCredentialsAuthenticationExceptionTests { + + private static final String CODE = "error.authentication.credentials.bad"; + private static final String MESSAGE = "GG"; + + @Test + public void verifyGetCode() { + final AuthenticationException e = new BadCredentialsAuthenticationException(); + assertEquals(CODE, e.getCode()); + assertEquals(CODE, e.toString()); + } + + @Test + public void verifyThrowableConstructor() { + final RuntimeException r = new RuntimeException(); + final BadCredentialsAuthenticationException e = new BadCredentialsAuthenticationException(r); + + assertEquals(CODE, e.getCode()); + assertEquals(r, e.getCause()); + } + + @Test + public void verifyCodeConstructor() { + final BadCredentialsAuthenticationException e = new BadCredentialsAuthenticationException(MESSAGE); + + assertEquals(MESSAGE, e.getCode()); + } + + @Test + public void verifyThrowableConstructorWithCode() { + final RuntimeException r = new RuntimeException(); + final BadCredentialsAuthenticationException e = new BadCredentialsAuthenticationException(MESSAGE, r); + + assertEquals(MESSAGE, e.getCode()); + assertEquals(r, e.getCause()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/BadPasswordAuthenticationExceptionTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/BadPasswordAuthenticationExceptionTests.java new file mode 100644 index 000000000000..10c670e69beb --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/BadPasswordAuthenticationExceptionTests.java @@ -0,0 +1,65 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public final class BadPasswordAuthenticationExceptionTests { + + private static final String CODE = "error.authentication.credentials.bad.usernameorpassword.password"; + private static final String MESSAGE = "GG"; + + @Test + public void verifyGetCode() { + final AuthenticationException e = new BadPasswordAuthenticationException(); + assertEquals(CODE, e.getCode()); + assertEquals(CODE, e.toString()); + } + + @Test + public void verifyThrowableConstructor() { + final RuntimeException r = new RuntimeException(); + final BadPasswordAuthenticationException e = new BadPasswordAuthenticationException(r); + + assertEquals(CODE, e.getCode()); + assertEquals(r, e.getCause()); + } + + @Test + public void verifyCodeConstructor() { + final BadPasswordAuthenticationException e = new BadPasswordAuthenticationException(MESSAGE); + + assertEquals(MESSAGE, e.getCode()); + } + + @Test + public void verifyThrowableConstructorWithCode() { + final RuntimeException r = new RuntimeException(); + final BadPasswordAuthenticationException e = new BadPasswordAuthenticationException(MESSAGE, r); + + assertEquals(MESSAGE, e.getCode()); + assertEquals(r, e.getCause()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/BadUsernameOrPasswordAuthenticationExceptionTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/BadUsernameOrPasswordAuthenticationExceptionTests.java new file mode 100644 index 000000000000..88cbb8567c15 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/BadUsernameOrPasswordAuthenticationExceptionTests.java @@ -0,0 +1,67 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * @author Scott Battaglia + * @since 3.1 + */ +public final class BadUsernameOrPasswordAuthenticationExceptionTests { + + private static final String CODE = "error.authentication.credentials.bad.usernameorpassword"; + private static final String MESSAGE = "GG"; + + @Test + public void verifyGetCode() { + final AuthenticationException e = new BadUsernameOrPasswordAuthenticationException(); + assertEquals(CODE, e.getCode()); + assertEquals(CODE, e.toString()); + } + + @Test + public void verifyThrowableConstructor() { + final RuntimeException r = new RuntimeException(); + final BadUsernameOrPasswordAuthenticationException e = + new BadUsernameOrPasswordAuthenticationException(r); + + assertEquals(CODE, e.getCode()); + assertEquals(r, e.getCause()); + } + + @Test + public void verifyCodeConstructor() { + final BadUsernameOrPasswordAuthenticationException e = + new BadUsernameOrPasswordAuthenticationException(MESSAGE); + + assertEquals(MESSAGE, e.getCode()); + } + + public void verifyThrowableConstructorWithCode() { + final RuntimeException r = new RuntimeException(); + final BadUsernameOrPasswordAuthenticationException e = + new BadUsernameOrPasswordAuthenticationException(MESSAGE, r); + + assertEquals(MESSAGE, e.getCode()); + assertEquals(r, e.getCause()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/BlockedCredentialsAuthenticationExceptionTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/BlockedCredentialsAuthenticationExceptionTests.java new file mode 100644 index 000000000000..988e807c5079 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/BlockedCredentialsAuthenticationExceptionTests.java @@ -0,0 +1,65 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * @author Scott Battaglia + * @since 3.1 + */ +public final class BlockedCredentialsAuthenticationExceptionTests { + + private static final String CODE = "error.authentication.credentials.blocked"; + private static final String MESSAGE = "GG"; + + @Test + public void verifyGetCode() { + final AuthenticationException e = new BlockedCredentialsAuthenticationException(); + assertEquals(CODE, e.getCode()); + assertEquals(CODE, e.toString()); + } + + @Test + public void verifyThrowableConstructor() { + final RuntimeException r = new RuntimeException(); + final BlockedCredentialsAuthenticationException e = new BlockedCredentialsAuthenticationException(r); + + assertEquals(CODE, e.getCode()); + assertEquals(r, e.getCause()); + } + + @Test + public void verifyCodeConstructor() { + final BlockedCredentialsAuthenticationException e = new BlockedCredentialsAuthenticationException(MESSAGE); + + assertEquals(MESSAGE, e.getCode()); + } + + @Test + public void verifyThrowableConstructorWithCode() { + final RuntimeException r = new RuntimeException(); + final BlockedCredentialsAuthenticationException e = new BlockedCredentialsAuthenticationException(MESSAGE, r); + + assertEquals(MESSAGE, e.getCode()); + assertEquals(r, e.getCause()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/ConvertCasePrincipalNameTransformerTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/ConvertCasePrincipalNameTransformerTests.java new file mode 100644 index 000000000000..14b6323b310e --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/ConvertCasePrincipalNameTransformerTests.java @@ -0,0 +1,56 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * Tests for the switch-case transformer. + * + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class ConvertCasePrincipalNameTransformerTests { + + @Test + public void verifyUpperCaseTranformerWithTrimAndDelegate() { + final PrefixSuffixPrincipalNameTransformer suffixTrans = new PrefixSuffixPrincipalNameTransformer(); + suffixTrans.setPrefix("a"); + suffixTrans.setSuffix("z"); + final ConvertCasePrincipalNameTransformer transformer = new ConvertCasePrincipalNameTransformer(suffixTrans); + transformer.setToUpperCase(true); + final String result = transformer.transform(" uid "); + assertEquals(result, "AUIDZ"); + } + + @Test + public void verifyUpperCaseTranformerWithTrim() { + final ConvertCasePrincipalNameTransformer transformer = new ConvertCasePrincipalNameTransformer(); + transformer.setToUpperCase(true); + final String result = transformer.transform(" uid "); + assertEquals(result, "UID"); + } + + @Test + public void verifyLowerCaseTranformerWithTrim() { + final String result = new ConvertCasePrincipalNameTransformer().transform(" UID "); + assertEquals(result, "uid"); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/DefaultPasswordEncoderTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/DefaultPasswordEncoderTests.java new file mode 100644 index 000000000000..3daf24362b54 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/DefaultPasswordEncoderTests.java @@ -0,0 +1,76 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +import static org.junit.Assert.*; + +import org.junit.Test; + + +/** + * @author Scott Battaglia + + * @since 3.0.0 + */ +public final class DefaultPasswordEncoderTests { + + private final PasswordEncoder passwordEncoder = new DefaultPasswordEncoder("MD5"); + + @Test + public void verifyNullPassword() { + assertEquals(null, this.passwordEncoder.encode(null)); + } + + @Test + public void verifyMd5Hash() { + assertEquals("1f3870be274f6c49b3e31a0c6728957f", this.passwordEncoder + .encode("apple")); + } + + @Test + public void verifySha1Hash() { + final PasswordEncoder pe = new DefaultPasswordEncoder("SHA1"); + + final String hash = pe.encode("this is a test"); + + assertEquals("fa26be19de6bff93f70bc2308434e4a440bbad02", hash); + + } + + @Test + public void verifySha1Hash2() { + final PasswordEncoder pe = new DefaultPasswordEncoder("SHA1"); + + final String hash = pe.encode("TEST of the SYSTEM"); + + assertEquals("82ae28dfad565dd9882b94498a271caa29025d5f", hash); + + } + + @Test + public void verifyInvalidEncodingType() { + final PasswordEncoder pe = new DefaultPasswordEncoder("scott"); + try { + pe.encode("test"); + fail("exception expected."); + } catch (final Exception e) { + return; + } + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/PlainTextPasswordEncoderTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/PlainTextPasswordEncoderTests.java new file mode 100644 index 000000000000..08299eb84119 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/PlainTextPasswordEncoderTests.java @@ -0,0 +1,44 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public final class PlainTextPasswordEncoderTests { + + private static final String CONST_TO_ENCODE = "CAS IS COOL"; + + private final PasswordEncoder passwordEncoder = new PlainTextPasswordEncoder(); + + @Test + public void verifyNullValueToTranslate() { + assertEquals(null, this.passwordEncoder.encode(null)); + } + + @Test + public void verifyValueToTranslate() { + assertEquals(CONST_TO_ENCODE, this.passwordEncoder.encode(CONST_TO_ENCODE)); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/UnknownUsernameAuthenticationExceptionTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/UnknownUsernameAuthenticationExceptionTests.java new file mode 100644 index 000000000000..15bac51e8158 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/UnknownUsernameAuthenticationExceptionTests.java @@ -0,0 +1,65 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public final class UnknownUsernameAuthenticationExceptionTests { + + private static final String CODE = "error.authentication.credentials.bad.usernameorpassword.username"; + private static final String MESSAGE = "GG"; + + @Test + public void verifyGetCode() { + final AuthenticationException e = new UnknownUsernameAuthenticationException(); + assertEquals(CODE, e.getCode()); + assertEquals(CODE, e.toString()); + } + + @Test + public void verifyThrowableConstructor() { + final RuntimeException r = new RuntimeException(); + final UnknownUsernameAuthenticationException e = new UnknownUsernameAuthenticationException(r); + + assertEquals(CODE, e.getCode()); + assertEquals(r, e.getCause()); + } + + @Test + public void verifyCodeConstructor() { + final UnknownUsernameAuthenticationException e = new UnknownUsernameAuthenticationException(MESSAGE); + + assertEquals(MESSAGE, e.getCode()); + } + + @Test + public void verifyThrowableConstructorWithCode() { + final RuntimeException r = new RuntimeException(); + final UnknownUsernameAuthenticationException e = new UnknownUsernameAuthenticationException(MESSAGE, r); + + assertEquals(MESSAGE, e.getCode()); + assertEquals(r, e.getCause()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/UnsupportedCredentialsExceptionTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/UnsupportedCredentialsExceptionTests.java new file mode 100644 index 000000000000..1409ba414033 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/UnsupportedCredentialsExceptionTests.java @@ -0,0 +1,52 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * @author Scott Battaglia + + * @since 3.0.0 + */ +public class UnsupportedCredentialsExceptionTests { + + private static final String CODE = "error.authentication.credentials.unsupported"; + + @Test + public void verifyNoParamConstructor() { + new UnsupportedCredentialsException(); + } + + @Test + public void verifyGetCode() { + assertEquals(CODE, + new UnsupportedCredentialsException().getCode()); + } + + @Test + public void verifyThrowableConstructor() { + final RuntimeException r = new RuntimeException(); + final UnsupportedCredentialsException e = new UnsupportedCredentialsException(r); + assertEquals(CODE, e.getCode()); + assertEquals(r, e.getCause()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/HttpBasedServiceCredentialsAuthenticationHandlerTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/HttpBasedServiceCredentialsAuthenticationHandlerTests.java new file mode 100644 index 000000000000..79e95eea261e --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/HttpBasedServiceCredentialsAuthenticationHandlerTests.java @@ -0,0 +1,87 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler.support; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.util.http.HttpClient; +import org.jasig.cas.util.http.SimpleHttpClientFactoryBean; +import org.junit.Before; +import org.junit.Test; + +import javax.security.auth.login.FailedLoginException; + +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public final class HttpBasedServiceCredentialsAuthenticationHandlerTests { + + private HttpBasedServiceCredentialsAuthenticationHandler authenticationHandler; + + @Before + public void setUp() throws Exception { + this.authenticationHandler = new HttpBasedServiceCredentialsAuthenticationHandler(); + this.authenticationHandler.setHttpClient(new SimpleHttpClientFactoryBean().getObject()); + } + + @Test + public void verifySupportsProperUserCredentials() { + assertTrue(this.authenticationHandler.supports(TestUtils.getHttpBasedServiceCredentials())); + } + + @Test + public void verifyDoesntSupportBadUserCredentials() { + assertFalse(this.authenticationHandler.supports(TestUtils.getCredentialsWithSameUsernameAndPassword())); + } + + @Test + public void verifyAcceptsProperCertificateCredentials() throws Exception { + assertNotNull(this.authenticationHandler.authenticate(TestUtils.getHttpBasedServiceCredentials())); + } + + @Test(expected = FailedLoginException.class) + public void verifyRejectsInProperCertificateCredentials() throws Exception { + this.authenticationHandler.authenticate( + TestUtils.getHttpBasedServiceCredentials("https://clearinghouse.ja-sig.org")); + } + + @Test + public void verifyAcceptsNonHttpsCredentials() throws Exception { + this.authenticationHandler.setHttpClient(new SimpleHttpClientFactoryBean().getObject()); + assertNotNull(this.authenticationHandler.authenticate( + TestUtils.getHttpBasedServiceCredentials("http://www.google.com"))); + } + + @Test(expected = FailedLoginException.class) + public void verifyNoAcceptableStatusCode() throws Exception { + this.authenticationHandler.authenticate( + TestUtils.getHttpBasedServiceCredentials("https://clue.acs.rutgers.edu")); + } + + @Test(expected = FailedLoginException.class) + public void verifyNoAcceptableStatusCodeButOneSet() throws Exception { + final SimpleHttpClientFactoryBean clientFactory = new SimpleHttpClientFactoryBean(); + clientFactory.setAcceptableCodes(new int[] {900}); + final HttpClient httpClient = clientFactory.getObject(); + this.authenticationHandler.setHttpClient(httpClient); + this.authenticationHandler.authenticate(TestUtils.getHttpBasedServiceCredentials("https://www.ja-sig.org")); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/JaasAuthenticationHandlerTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/JaasAuthenticationHandlerTests.java new file mode 100644 index 000000000000..ad150e6d64f8 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/JaasAuthenticationHandlerTests.java @@ -0,0 +1,79 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler.support; + +import javax.security.auth.login.LoginException; + +import org.jasig.cas.TestUtils; +import org.junit.Before; +import org.junit.Test; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.Assert.*; + +/** + * @author Marvin S. Addison + * @since 3.0.0 + */ +public class JaasAuthenticationHandlerTests { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private JaasAuthenticationHandler handler; + + @Before + public void setUp() throws Exception { + String pathPrefix = System.getProperty("user.dir"); + pathPrefix = !pathPrefix.contains("cas-server-core") ? pathPrefix + + "/cas-server-core" : pathPrefix; + logger.info("PATH PREFIX: {}", pathPrefix); + + final String pathToConfig = pathPrefix + + "/src/test/resources/org/jasig/cas/authentication/handler/support/jaas.conf"; + System.setProperty("java.security.auth.login.config", "="+pathToConfig); + this.handler = new JaasAuthenticationHandler(); + } + + @Test(expected = LoginException.class) + public void verifyWithAlternativeRealm() throws Exception { + + this.handler.setRealm("TEST"); + this.handler.authenticate(TestUtils.getCredentialsWithDifferentUsernameAndPassword("test", "test1")); + } + + @Test + public void verifyWithAlternativeRealmAndValidCredentials() throws Exception { + this.handler.setRealm("TEST"); + assertNotNull(this.handler.authenticate( + TestUtils.getCredentialsWithDifferentUsernameAndPassword("test", "test"))); + } + + @Test + public void verifyWithValidCredenials() throws Exception { + assertNotNull(this.handler.authenticate(TestUtils.getCredentialsWithSameUsernameAndPassword())); + } + + @Test(expected = LoginException.class) + public void verifyWithInvalidCredentials() throws Exception { + this.handler.authenticate(TestUtils.getCredentialsWithDifferentUsernameAndPassword("test", "test1")); + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/MockLoginModule.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/MockLoginModule.java new file mode 100644 index 000000000000..1980d032c4cb --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/MockLoginModule.java @@ -0,0 +1,71 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler.support; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; +import java.util.Map; + +/** + * @author Marvin S. Addison + * @since 3.0.0 + */ +public class MockLoginModule implements LoginModule { + private CallbackHandler callbackHandler; + + public void initialize(final Subject subject, final CallbackHandler handler, final Map arg2, + final Map arg3) { + this.callbackHandler = handler; + } + + public boolean login() throws LoginException { + final Callback[] callbacks = new Callback[] {new NameCallback("f"), new PasswordCallback("f", false)}; + try { + this.callbackHandler.handle(callbacks); + } catch (final Exception e) { + throw new LoginException(); + } + + final String userName = ((NameCallback) callbacks[0]).getName(); + final String password = new String(((PasswordCallback) callbacks[1]).getPassword()); + + if ("test".equals(userName) && "test".equals(password)) { + return true; + } + + throw new LoginException(); + } + + public boolean commit() throws LoginException { + return true; + } + + public boolean abort() throws LoginException { + return true; + } + + public boolean logout() throws LoginException { + return true; + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/SimpleTestUsernamePasswordAuthenticationHandler.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/SimpleTestUsernamePasswordAuthenticationHandler.java new file mode 100644 index 000000000000..6aea04cb88f7 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/SimpleTestUsernamePasswordAuthenticationHandler.java @@ -0,0 +1,117 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler.support; + +import java.security.GeneralSecurityException; +import java.util.HashMap; +import java.util.Map; +import javax.security.auth.login.AccountLockedException; +import javax.security.auth.login.CredentialExpiredException; +import javax.security.auth.login.FailedLoginException; + +import org.jasig.cas.authentication.AccountDisabledException; +import org.jasig.cas.authentication.AuthenticationHandler; +import org.jasig.cas.authentication.BasicCredentialMetaData; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.InvalidLoginLocationException; +import org.jasig.cas.authentication.InvalidLoginTimeException; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.Credential; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +/** + * Simple test implementation of a AuthenticationHandler that returns true if + * the username and password match. This class should never be enabled in a + * production environment and is only designed to facilitate unit testing and + * load testing. + * + * @author Scott Battagliaa + * @author Marvin S. Addison + * @since 3.0.0 + */ +public final class SimpleTestUsernamePasswordAuthenticationHandler implements AuthenticationHandler { + /** Default mapping of special usernames to exceptions raised when that user attempts authentication. */ + private static final Map DEFAULT_USERNAME_ERROR_MAP = new HashMap<>(); + + /** Instance of logging for subclasses. */ + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** Map of special usernames to exceptions that are raised when a user with that name attempts authentication. */ + private Map usernameErrorMap = DEFAULT_USERNAME_ERROR_MAP; + + + static { + DEFAULT_USERNAME_ERROR_MAP.put("accountDisabled", new AccountDisabledException("Account disabled")); + DEFAULT_USERNAME_ERROR_MAP.put("accountLocked", new AccountLockedException("Account locked")); + DEFAULT_USERNAME_ERROR_MAP.put("badHours", new InvalidLoginTimeException("Invalid logon hours")); + DEFAULT_USERNAME_ERROR_MAP.put("badWorkstation", new InvalidLoginLocationException("Invalid workstation")); + DEFAULT_USERNAME_ERROR_MAP.put("passwordExpired", new CredentialExpiredException("Password expired")); + } + + public SimpleTestUsernamePasswordAuthenticationHandler() { + logger.warn( + "{} is only to be used in a testing environment. NEVER enable this in a production environment.", + getName()); + } + + public void setUsernameErrorMap(final Map map) { + this.usernameErrorMap = map; + } + + @Override + public HandlerResult authenticate(final Credential credential) + throws GeneralSecurityException, PreventedException { + + final UsernamePasswordCredential usernamePasswordCredential = (UsernamePasswordCredential) credential; + final String username = usernamePasswordCredential.getUsername(); + final String password = usernamePasswordCredential.getPassword(); + + final Exception exception = this.usernameErrorMap.get(username); + if (exception instanceof GeneralSecurityException) { + throw (GeneralSecurityException) exception; + } else if (exception instanceof PreventedException) { + throw (PreventedException) exception; + } else if (exception instanceof RuntimeException) { + throw (RuntimeException) exception; + } else if (exception != null) { + logger.debug("Cannot throw checked exception {} since it is not declared by method signature.", exception); + } + + if (StringUtils.hasText(username) && StringUtils.hasText(password) && username.equals(password)) { + logger.debug("User [{}] was successfully authenticated.", username); + return new DefaultHandlerResult(this, new BasicCredentialMetaData(credential)); + } + logger.debug("User [{}] failed authentication", username); + throw new FailedLoginException(); + } + + @Override + public boolean supports(final Credential credential) { + return credential instanceof UsernamePasswordCredential; + } + + @Override + public String getName() { + return getClass().getSimpleName(); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/SimpleTestUsernamePasswordHandlerTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/SimpleTestUsernamePasswordHandlerTests.java new file mode 100644 index 000000000000..9b42a9e1e503 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/SimpleTestUsernamePasswordHandlerTests.java @@ -0,0 +1,67 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler.support; + +import javax.security.auth.login.FailedLoginException; + +import static org.junit.Assert.*; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.HandlerResult; +import org.junit.Before; +import org.junit.Test; + +/** + * Test of the simple username/password handler. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public final class SimpleTestUsernamePasswordHandlerTests { + + private SimpleTestUsernamePasswordAuthenticationHandler authenticationHandler; + + @Before + public void setUp() throws Exception { + this.authenticationHandler = new SimpleTestUsernamePasswordAuthenticationHandler(); + } + + @Test + public void verifySupportsProperUserCredentials() { + assertTrue(this.authenticationHandler.supports(TestUtils.getCredentialsWithSameUsernameAndPassword())); + } + + @Test + public void verifyDoesntSupportBadUserCredentials() { + assertFalse(this.authenticationHandler.supports(TestUtils.getHttpBasedServiceCredentials())); + } + + @Test + public void verifyValidUsernamePassword() throws Exception { + final HandlerResult result = authenticationHandler.authenticate( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + assertEquals("SimpleTestUsernamePasswordAuthenticationHandler", result.getHandlerName()); + } + + @Test(expected = FailedLoginException.class) + public void verifyInvalidUsernamePassword() throws Exception { + this.authenticationHandler.authenticate(TestUtils.getCredentialsWithDifferentUsernameAndPassword()); + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/CachingPrincipalAttributesRepositoryTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/CachingPrincipalAttributesRepositoryTests.java new file mode 100644 index 000000000000..4ad0e15608a3 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/CachingPrincipalAttributesRepositoryTests.java @@ -0,0 +1,136 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import org.jasig.services.persondir.IPersonAttributeDao; +import org.jasig.services.persondir.IPersonAttributes; +import org.jasig.services.persondir.support.merger.MultivaluedAttributeMerger; +import org.jasig.services.persondir.support.merger.NoncollidingAttributeAdder; +import org.jasig.services.persondir.support.merger.ReplacingAttributeAdder; +import org.junit.Before; +import org.junit.Test; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Handles tests for {@link CachingPrincipalAttributesRepository}. + * @author Misagh Moayyed + * @since 4.1 + */ +public class CachingPrincipalAttributesRepositoryTests { + private Map> attributes; + + private IPersonAttributeDao dao; + + private final PrincipalFactory principalFactory = new DefaultPrincipalFactory(); + + private Principal principal; + + @Before + public void setup() { + attributes = new HashMap<>(); + attributes.put("a1", new ArrayList(Arrays.asList(new Object[]{"v1", "v2", "v3"}))); + attributes.put("mail", new ArrayList(Arrays.asList(new Object[]{"final@example.com"}))); + attributes.put("a6", new ArrayList(Arrays.asList(new Object[]{"v16", "v26", "v63"}))); + attributes.put("a2", new ArrayList(Arrays.asList(new Object[]{"v4"}))); + attributes.put("username", new ArrayList(Arrays.asList(new Object[]{"uid"}))); + + this.dao = mock(IPersonAttributeDao.class); + final IPersonAttributes person = mock(IPersonAttributes.class); + when(person.getName()).thenReturn("uid"); + when(person.getAttributes()).thenReturn(attributes); + when(dao.getPerson(any(String.class))).thenReturn(person); + + this.principal = this.principalFactory.createPrincipal("uid", + Collections.singletonMap("mail", + new ArrayList(Arrays.asList(new Object[]{"final@school.com"})))); + } + + @Test + public void checkExpiredCachedAttributes() throws Exception { + assertEquals(this.principal.getAttributes().size(), 1); + final PrincipalAttributesRepository repository = new CachingPrincipalAttributesRepository(this.dao, + TimeUnit.MILLISECONDS, 100); + assertEquals(repository.getAttributes(this.principal).size(), this.attributes.size()); + assertTrue(repository.getAttributes(this.principal).containsKey("mail")); + Thread.sleep(200); + this.attributes.remove("mail"); + assertTrue(repository.getAttributes(this.principal).containsKey("a2")); + assertFalse(repository.getAttributes(this.principal).containsKey("mail")); + ((Closeable) repository).close(); + } + + @Test + public void ensureCachedAttributesWithUpdate() throws Exception { + final PrincipalAttributesRepository repository = new CachingPrincipalAttributesRepository(this.dao, + TimeUnit.SECONDS, 5); + assertEquals(repository.getAttributes(this.principal).size(), this.attributes.size()); + assertTrue(repository.getAttributes(this.principal).containsKey("mail")); + + attributes.clear(); + assertTrue(repository.getAttributes(this.principal).containsKey("mail")); + ((Closeable) repository).close(); + } + + @Test + public void verifyMergingStrategyWithNoncollidingAttributeAdder() throws Exception { + final CachingPrincipalAttributesRepository repository = new CachingPrincipalAttributesRepository(this.dao, + TimeUnit.SECONDS, 5); + repository.setMergingStrategy(new NoncollidingAttributeAdder()); + + assertTrue(repository.getAttributes(this.principal).containsKey("mail")); + assertEquals(repository.getAttributes(this.principal).get("mail").toString(), "final@school.com"); + ((Closeable) repository).close(); + } + + @Test + public void verifyMergingStrategyWithReplacingAttributeAdder() throws Exception { + final CachingPrincipalAttributesRepository repository = new CachingPrincipalAttributesRepository(this.dao, + TimeUnit.SECONDS, 5); + repository.setMergingStrategy(new ReplacingAttributeAdder()); + + assertTrue(repository.getAttributes(this.principal).containsKey("mail")); + assertEquals(repository.getAttributes(this.principal).get("mail").toString(), "final@example.com"); + ((Closeable) repository).close(); + } + + @Test + public void verifyMergingStrategyWithMultivaluedAttributeMerger() throws Exception { + final CachingPrincipalAttributesRepository repository = new CachingPrincipalAttributesRepository(this.dao, + TimeUnit.SECONDS, 5); + repository.setMergingStrategy(new MultivaluedAttributeMerger()); + + assertTrue(repository.getAttributes(this.principal).get("mail") instanceof List); + + final List values = (List) repository.getAttributes(this.principal).get("mail"); + assertTrue(values.contains("final@example.com")); + assertTrue(values.contains("final@school.com")); + ((Closeable) repository).close(); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/ChainingPrincipalResolverTest.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/ChainingPrincipalResolverTest.java new file mode 100644 index 000000000000..38992ff80a35 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/ChainingPrincipalResolverTest.java @@ -0,0 +1,82 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import org.jasig.cas.authentication.Credential; +import org.junit.Test; +import org.mockito.ArgumentMatcher; + +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Unit test for {@link ChainingPrincipalResolver}. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class ChainingPrincipalResolverTest { + + private final PrincipalFactory principalFactory = new DefaultPrincipalFactory(); + + @Test + public void examineSupports() throws Exception { + final Credential credential = mock(Credential.class); + when(credential.getId()).thenReturn("a"); + + final PrincipalResolver resolver1 = mock(PrincipalResolver.class); + when(resolver1.supports(eq(credential))).thenReturn(true); + + final PrincipalResolver resolver2 = mock(PrincipalResolver.class); + when(resolver2.supports(eq(credential))).thenReturn(false); + + final ChainingPrincipalResolver resolver = new ChainingPrincipalResolver(); + resolver.setChain(Arrays.asList(resolver1, resolver2)); + assertTrue(resolver.supports(credential)); + } + + @Test + public void examineResolve() throws Exception { + final Credential credential = mock(Credential.class); + when(credential.getId()).thenReturn("input"); + + final PrincipalResolver resolver1 = mock(PrincipalResolver.class); + when(resolver1.supports(eq(credential))).thenReturn(true); + when(resolver1.resolve((eq(credential)))).thenReturn(principalFactory.createPrincipal("output")); + + final PrincipalResolver resolver2 = mock(PrincipalResolver.class); + when(resolver2.supports(any(Credential.class))).thenReturn(false); + when(resolver2.resolve(argThat(new ArgumentMatcher() { + @Override + public boolean matches(final Object o) { + return "output".equals(((Credential) o).getId()); + } + }))).thenReturn(principalFactory.createPrincipal("final", Collections.singletonMap("mail", "final@example.com"))); + + final ChainingPrincipalResolver resolver = new ChainingPrincipalResolver(); + resolver.setChain(Arrays.asList(resolver1, resolver2)); + final Principal principal = resolver.resolve(credential); + assertEquals("final", principal.getId()); + assertEquals("final@example.com", principal.getAttributes().get("mail")); + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/DefaultPrincipalAttributesRepositoryTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/DefaultPrincipalAttributesRepositoryTests.java new file mode 100644 index 000000000000..be993138dec3 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/DefaultPrincipalAttributesRepositoryTests.java @@ -0,0 +1,49 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import org.junit.Test; + +import java.util.Collections; + +import static org.junit.Assert.*; + +/** + * Handles tests for {@link DefaultPrincipalAttributesRepository}. + * @author Misagh Moayyed + * @since 4.1 + */ +public class DefaultPrincipalAttributesRepositoryTests { + private final PrincipalFactory factory = new DefaultPrincipalFactory(); + + @Test + public void checkNoAttributes() { + final PrincipalAttributesRepository rep = new DefaultPrincipalAttributesRepository(); + assertEquals(rep.getAttributes(this.factory.createPrincipal("uid")).size(), 0); + } + + @Test + public void checkInitialAttributes() { + final Principal p = this.factory.createPrincipal("uid", + Collections.singletonMap("mail", "final@example.com")); + final PrincipalAttributesRepository rep = new DefaultPrincipalAttributesRepository(); + assertEquals(rep.getAttributes(p).size(), 1); + assertTrue(rep.getAttributes(p).containsKey("mail")); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/DefaultPrincipalFactoryTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/DefaultPrincipalFactoryTests.java new file mode 100644 index 000000000000..c2e959c2b0f1 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/DefaultPrincipalFactoryTests.java @@ -0,0 +1,58 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import org.junit.Test; + +import java.util.Collections; + +import static org.junit.Assert.*; + +/** + * Handles tests for {@link DefaultPrincipalFactory}. + * @author Misagh Moayyed + * @since 4.1 + */ +public class DefaultPrincipalFactoryTests { + @Test + public void checkCreatingSimplePrincipal() { + final PrincipalFactory f = new DefaultPrincipalFactory(); + final Principal p = f.createPrincipal("uid"); + assertEquals(p.getId(), "uid"); + assertEquals(p.getAttributes().size(), 0); + } + + @Test + public void checkCreatingSimplePrincipalWithAttributes() { + final PrincipalFactory f = new DefaultPrincipalFactory(); + final Principal p = f.createPrincipal("uid", Collections.singletonMap("mail", "final@example.com")); + assertEquals(p.getId(), "uid"); + assertEquals(p.getAttributes().size(), 1); + assertTrue(p.getAttributes().containsKey("mail")); + } + + @Test + public void checkCreatingSimplePrincipalWithDefaultRepository() { + final PrincipalFactory f = new DefaultPrincipalFactory(); + final Principal p = f.createPrincipal("uid"); + assertEquals(p.getId(), "uid"); + assertEquals(p.getAttributes().size(), 0); + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/RememberMeAuthenticationMetaDataPopulatorTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/RememberMeAuthenticationMetaDataPopulatorTests.java new file mode 100644 index 000000000000..83c6b3fb929a --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/RememberMeAuthenticationMetaDataPopulatorTests.java @@ -0,0 +1,90 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.AuthenticationBuilder; +import org.jasig.cas.authentication.AuthenticationHandler; +import org.jasig.cas.authentication.BasicCredentialMetaData; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.CredentialMetaData; +import org.jasig.cas.authentication.DefaultAuthenticationBuilder; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.RememberMeCredential; +import org.jasig.cas.authentication.RememberMeUsernamePasswordCredential; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * + * @author Scott Battaglia + * @since 3.2.1 + * + */ +public class RememberMeAuthenticationMetaDataPopulatorTests { + + private final RememberMeAuthenticationMetaDataPopulator p = new RememberMeAuthenticationMetaDataPopulator(); + + @Test + public void verifyWithTrueRememberMeCredentials() { + final RememberMeUsernamePasswordCredential c = new RememberMeUsernamePasswordCredential(); + c.setRememberMe(true); + final AuthenticationBuilder builder = newBuilder(c); + final Authentication auth = builder.build(); + + assertEquals(true, auth.getAttributes().get(RememberMeCredential.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME)); + } + + @Test + public void verifyWithFalseRememberMeCredentials() { + final RememberMeUsernamePasswordCredential c = new RememberMeUsernamePasswordCredential(); + c.setRememberMe(false); + final AuthenticationBuilder builder = newBuilder(c); + final Authentication auth = builder.build(); + + assertNull(auth.getAttributes().get(RememberMeCredential.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME)); + } + + @Test + public void verifyWithoutRememberMeCredentials() { + final AuthenticationBuilder builder = newBuilder(TestUtils.getCredentialsWithSameUsernameAndPassword()); + final Authentication auth = builder.build(); + + assertNull(auth.getAttributes().get(RememberMeCredential.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME)); + } + + private AuthenticationBuilder newBuilder(final Credential credential) { + final CredentialMetaData meta = new BasicCredentialMetaData(new UsernamePasswordCredential()); + final AuthenticationHandler handler = new SimpleTestUsernamePasswordAuthenticationHandler(); + final AuthenticationBuilder builder = new DefaultAuthenticationBuilder(TestUtils.getPrincipal()) + .addCredential(meta) + .addSuccess("test", new DefaultHandlerResult(handler, meta)); + + if (this.p.supports(credential)) { + this.p.populateAttributes(builder, credential); + } + return builder; + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/ResponseTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/ResponseTests.java new file mode 100644 index 000000000000..1a1c3acc4115 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/ResponseTests.java @@ -0,0 +1,90 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + + +import static org.junit.Assert.*; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +/** + * @author Scott Battaglia + * @since 3.4.4 + */ +public class ResponseTests { + + @Test + public void verifyConstructionWithoutFragmentAndNoQueryString() { + final String url = "http://localhost:8080/foo"; + final Map attributes = new HashMap<>(); + attributes.put("ticket", "foobar"); + final Response response = DefaultResponse.getRedirectResponse(url, attributes); + assertEquals(url + "?ticket=foobar", response.getUrl()); + } + + @Test + public void verifyConstructionWithoutFragmentButHasQueryString() { + final String url = "http://localhost:8080/foo?test=boo"; + final Map attributes = new HashMap<>(); + attributes.put("ticket", "foobar"); + final Response response = DefaultResponse.getRedirectResponse(url, attributes); + assertEquals(url + "&ticket=foobar", response.getUrl()); + } + + @Test + public void verifyConstructionWithFragmentAndQueryString() { + final String url = "http://localhost:8080/foo?test=boo#hello"; + final Map attributes = new HashMap<>(); + attributes.put("ticket", "foobar"); + final Response response = DefaultResponse.getRedirectResponse(url, attributes); + assertEquals("http://localhost:8080/foo?test=boo&ticket=foobar#hello", response.getUrl()); + } + + @Test + public void verifyConstructionWithFragmentAndNoQueryString() { + final String url = "http://localhost:8080/foo#hello"; + final Map attributes = new HashMap<>(); + attributes.put("ticket", "foobar"); + final Response response = DefaultResponse.getRedirectResponse(url, attributes); + assertEquals("http://localhost:8080/foo?ticket=foobar#hello", response.getUrl()); + + } + + @Test + public void verifyUrlSanitization() { + final String url = "https://www.example.com\r\nLocation: javascript:\r\n\r\n"; + final Map attributes = new HashMap<>(); + attributes.put("ticket", "ST-12345"); + final Response response = DefaultResponse.getRedirectResponse(url, attributes); + assertEquals("https://www.example.com Location: javascript: ?ticket=ST-12345", + response.getUrl()); + } + + @Test + public void verifyUrlWithUnicode() { + final String url = "https://www.example.com/πολιτικῶν"; + final Map attributes = new HashMap<>(); + attributes.put("ticket", "ST-12345"); + final Response response = DefaultResponse.getRedirectResponse(url, attributes); + assertEquals("https://www.example.com/πολιτικῶν?ticket=ST-12345", response.getUrl()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/ShibbolethCompatiblePersistentIdGeneratorTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/ShibbolethCompatiblePersistentIdGeneratorTests.java new file mode 100644 index 000000000000..689e2961cc7f --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/ShibbolethCompatiblePersistentIdGeneratorTests.java @@ -0,0 +1,43 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import org.jasig.cas.TestUtils; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; + +/** + * @author Scott Battaglia + * @since 3.1 + */ +public class ShibbolethCompatiblePersistentIdGeneratorTests { + + @Test + public void verifyGenerator() { + final ShibbolethCompatiblePersistentIdGenerator generator = + new ShibbolethCompatiblePersistentIdGenerator("scottssalt"); + + final Principal p = TestUtils.getPrincipal(); + final String value = generator.generate(p, TestUtils.getService()); + + assertNotNull(value); + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/SimplePrincipalFactoryTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/SimplePrincipalFactoryTests.java new file mode 100644 index 000000000000..63a4b2b503bf --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/SimplePrincipalFactoryTests.java @@ -0,0 +1,45 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.authentication.principal; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +/** + * @author Misagh Moayyed + * @since 3.0.0 + */ +public class SimplePrincipalFactoryTests { + @Test + public void checkPrincipalCreation() { + final PrincipalFactory fact = new DefaultPrincipalFactory(); + final Map map = new HashMap<>(); + map.put("a1", "v1"); + map.put("a2", "v3"); + + final Principal p = fact.createPrincipal("user", map); + assertTrue(p instanceof SimplePrincipal); + assertEquals(p.getAttributes(), map); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/SimpleWebApplicationServiceImplTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/SimpleWebApplicationServiceImplTests.java new file mode 100644 index 000000000000..dee8c1b6bee8 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/SimpleWebApplicationServiceImplTests.java @@ -0,0 +1,99 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +/** + * + * @author Scott Battaglia + * @author Arnaud Lesueur + * @since 3.1 + * + */ +public class SimpleWebApplicationServiceImplTests { + + @Test + public void verifyResponse() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("service", "service"); + final SimpleWebApplicationServiceImpl impl = SimpleWebApplicationServiceImpl.createServiceFrom(request); + + final Response response = impl.getResponse("ticketId"); + assertNotNull(response); + assertEquals(Response.ResponseType.REDIRECT, response.getResponseType()); + } + + @Test + public void verifyCreateSimpleWebApplicationServiceImplFromServiceAttribute() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setAttribute("service", "service"); + final SimpleWebApplicationServiceImpl impl = SimpleWebApplicationServiceImpl.createServiceFrom(request); + assertNotNull(impl); + } + + @Test + public void verifyResponseForJsession() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("service", "http://www.cnn.com/;jsession=test"); + final WebApplicationService impl = SimpleWebApplicationServiceImpl.createServiceFrom(request); + + assertEquals("http://www.cnn.com/", impl.getId()); + } + + @Test + public void verifyResponseWithNoTicket() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("service", "service"); + final WebApplicationService impl = SimpleWebApplicationServiceImpl.createServiceFrom(request); + + final Response response = impl.getResponse(null); + assertNotNull(response); + assertEquals(Response.ResponseType.REDIRECT, response.getResponseType()); + assertFalse(response.getUrl().contains("ticket=")); + } + + @Test + public void verifyResponseWithNoTicketAndNoParameterInServiceURL() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("service", "http://foo.com/"); + final WebApplicationService impl = SimpleWebApplicationServiceImpl.createServiceFrom(request); + + final Response response = impl.getResponse(null); + assertNotNull(response); + assertEquals(Response.ResponseType.REDIRECT, response.getResponseType()); + assertFalse(response.getUrl().contains("ticket=")); + assertEquals("http://foo.com/", response.getUrl()); + } + + @Test + public void verifyResponseWithNoTicketAndOneParameterInServiceURL() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("service", "http://foo.com/?param=test"); + final WebApplicationService impl = SimpleWebApplicationServiceImpl.createServiceFrom(request); + + final Response response = impl.getResponse(null); + assertNotNull(response); + assertEquals(Response.ResponseType.REDIRECT, response.getResponseType()); + assertEquals("http://foo.com/?param=test", response.getUrl()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/support/DefaultCasAttributeEncoderTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/support/DefaultCasAttributeEncoderTests.java new file mode 100644 index 000000000000..d96989e92da3 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/support/DefaultCasAttributeEncoderTests.java @@ -0,0 +1,93 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.authentication.support; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.web.view.CasViewConstants; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +/** + * This is test cases for {@link DefaultCasAttributeEncoder}. + * + * @author Misagh Moayyed + * @since 4.1 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations= {"/core-context.xml", "/WEB-INF/cas-servlet.xml"}) +public class DefaultCasAttributeEncoderTests { + + private Map attributes; + + @Autowired + private ServicesManager servicesManager; + + @Before + public void before() { + this.attributes = new HashMap<>(); + for (int i = 0; i < 3; i++) { + this.attributes.put("attr" + i, newSingleAttribute("value" + i)); + } + this.attributes.put(CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET, newSingleAttribute("PGT-1234567")); + this.attributes.put(CasViewConstants.MODEL_ATTRIBUTE_NAME_PRINCIPAL_CREDENTIAL, newSingleAttribute("PrincipalPassword")); + } + + private Collection newSingleAttribute(final String attr) { + return Collections.singleton(attr); + } + + @Test + public void checkNoPublicKeyDefined() { + final Service service = TestUtils.getService("testDefault"); + final CasAttributeEncoder encoder = new DefaultCasAttributeEncoder(this.servicesManager); + final Map encoded = encoder.encodeAttributes(this.attributes, service); + assertEquals(encoded.size(), this.attributes.size() - 2); + } + + @Test + public void checkAttributesEncodedCorrectly() { + final Service service = TestUtils.getService("testencryption"); + final CasAttributeEncoder encoder = new DefaultCasAttributeEncoder(this.servicesManager); + final Map encoded = encoder.encodeAttributes(this.attributes, service); + assertEquals(encoded.size(), this.attributes.size()); + checkEncryptedValues(CasViewConstants.MODEL_ATTRIBUTE_NAME_PRINCIPAL_CREDENTIAL, encoded); + checkEncryptedValues(CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET, encoded); + } + + private void checkEncryptedValues(final String name, final Map encoded) { + final String v1 = ((Collection) this.attributes.get( + name)).iterator().next().toString(); + final String v2 = (String) encoded.get(name); + assertNotEquals(v1, v2); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/logout/LogoutManagerImplTests.java b/cas-server-core/src/test/java/org/jasig/cas/logout/LogoutManagerImplTests.java new file mode 100644 index 000000000000..5553336fc868 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/logout/LogoutManagerImplTests.java @@ -0,0 +1,171 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.logout; + +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.services.LogoutType; +import org.jasig.cas.services.RegisteredServiceImpl; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.util.http.HttpClient; +import org.jasig.cas.util.http.HttpMessage; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.net.URL; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + + +/** + * @author Jerome Leleu + * @since 4.0.0 + */ +@RunWith(JUnit4.class) +public class LogoutManagerImplTests { + + private static final String ID = "id"; + + private static final String URL = "http://www.github.com"; + + private LogoutManagerImpl logoutManager; + + @Mock + private TicketGrantingTicket tgt; + + private Map services; + + private SimpleWebApplicationServiceImpl simpleWebApplicationServiceImpl; + + private RegisteredServiceImpl registeredService; + + @Mock + private ServicesManager servicesManager; + + @Mock + private HttpClient client; + + public LogoutManagerImplTests() { + MockitoAnnotations.initMocks(this); + } + + @Before + public void setUp() { + + when(client.isValidEndPoint(any(String.class))).thenReturn(true); + when(client.isValidEndPoint(any(URL.class))).thenReturn(true); + when(client.sendMessageToEndPoint(any(HttpMessage.class))).thenReturn(true); + this.logoutManager = new LogoutManagerImpl(servicesManager, client, new SamlCompliantLogoutMessageCreator()); + + this.services = new HashMap<>(); + this.simpleWebApplicationServiceImpl = new SimpleWebApplicationServiceImpl(URL); + this.services.put(ID, this.simpleWebApplicationServiceImpl); + when(this.tgt.getServices()).thenReturn(this.services); + + this.registeredService = new RegisteredServiceImpl(); + when(servicesManager.findServiceBy(this.simpleWebApplicationServiceImpl)).thenReturn(this.registeredService); + } + + @Test + public void verifyServiceLogoutUrlIsUsed() throws Exception { + this.registeredService.setLogoutUrl(new URL("https://www.apereo.org")); + final Collection logoutRequests = this.logoutManager.performLogout(tgt); + final LogoutRequest logoutRequest = logoutRequests.iterator().next(); + assertEquals(logoutRequest.getLogoutUrl(), this.registeredService.getLogoutUrl()); + } + + @Test + public void verifyLogoutDisabled() { + this.logoutManager.setSingleLogoutCallbacksDisabled(true); + final Collection logoutRequests = this.logoutManager.performLogout(tgt); + assertEquals(0, logoutRequests.size()); + } + + @Test + public void verifyLogoutAlreadyLoggedOut() { + this.simpleWebApplicationServiceImpl.setLoggedOutAlready(true); + final Collection logoutRequests = this.logoutManager.performLogout(tgt); + assertEquals(0, logoutRequests.size()); + } + + @Test + public void verifyLogoutTypeNotSet() { + final Collection logoutRequests = this.logoutManager.performLogout(tgt); + assertEquals(1, logoutRequests.size()); + final LogoutRequest logoutRequest = logoutRequests.iterator().next(); + assertEquals(ID, logoutRequest.getTicketId()); + assertEquals(this.simpleWebApplicationServiceImpl, logoutRequest.getService()); + assertEquals(LogoutRequestStatus.SUCCESS, logoutRequest.getStatus()); + } + + @Test + public void verifyLogoutTypeBack() { + this.registeredService.setLogoutType(LogoutType.BACK_CHANNEL); + final Collection logoutRequests = this.logoutManager.performLogout(tgt); + assertEquals(1, logoutRequests.size()); + final LogoutRequest logoutRequest = logoutRequests.iterator().next(); + assertEquals(ID, logoutRequest.getTicketId()); + assertEquals(this.simpleWebApplicationServiceImpl, logoutRequest.getService()); + assertEquals(LogoutRequestStatus.SUCCESS, logoutRequest.getStatus()); + } + + @Test + public void verifyLogoutTypeNone() { + this.registeredService.setLogoutType(LogoutType.NONE); + final Collection logoutRequests = this.logoutManager.performLogout(tgt); + assertEquals(0, logoutRequests.size()); + } + + @Test + public void verifyLogoutTypeNull() { + this.registeredService.setLogoutType(null); + final Collection logoutRequests = this.logoutManager.performLogout(tgt); + assertEquals(1, logoutRequests.size()); + final LogoutRequest logoutRequest = logoutRequests.iterator().next(); + assertEquals(ID, logoutRequest.getTicketId()); + } + + @Test + public void verifyLogoutTypeFront() { + this.registeredService.setLogoutType(LogoutType.FRONT_CHANNEL); + final Collection logoutRequests = this.logoutManager.performLogout(tgt); + assertEquals(1, logoutRequests.size()); + final LogoutRequest logoutRequest = logoutRequests.iterator().next(); + assertEquals(ID, logoutRequest.getTicketId()); + assertEquals(this.simpleWebApplicationServiceImpl, logoutRequest.getService()); + assertEquals(LogoutRequestStatus.NOT_ATTEMPTED, logoutRequest.getStatus()); + } + + @Test + public void verifyAsynchronousLogout() { + this.registeredService.setLogoutType(LogoutType.BACK_CHANNEL); + this.logoutManager.setAsynchronous(false); + final Collection logoutRequests = this.logoutManager.performLogout(tgt); + assertEquals(1, logoutRequests.size()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/logout/SamlCompliantLogoutMessageCreatorTests.java b/cas-server-core/src/test/java/org/jasig/cas/logout/SamlCompliantLogoutMessageCreatorTests.java new file mode 100644 index 000000000000..2078b9bfb564 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/logout/SamlCompliantLogoutMessageCreatorTests.java @@ -0,0 +1,68 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.logout; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.principal.SingleLogoutService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URL; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Marvin S. Addison + * @since 4.0.0 + */ +@RunWith(JUnit4.class) +public class SamlCompliantLogoutMessageCreatorTests { + + private final LogoutMessageCreator builder = new SamlCompliantLogoutMessageCreator(); + + @Test + public void verifyMessageBuilding() throws Exception { + + final SingleLogoutService service = mock(SingleLogoutService.class); + when(service.getOriginalUrl()).thenReturn(TestUtils.CONST_TEST_URL); + final URL logoutUrl = new URL(service.getOriginalUrl()); + final DefaultLogoutRequest request = new DefaultLogoutRequest("TICKET-ID", service, logoutUrl); + + final String msg = builder.create(request); + + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + final DocumentBuilder builder = factory.newDocumentBuilder(); + + final InputStream is = new ByteArrayInputStream(msg.getBytes()); + final Document document = builder.parse(is); + + final NodeList list = document.getDocumentElement().getElementsByTagName("samlp:SessionIndex"); + assertEquals(list.getLength(), 1); + + assertEquals(list.item(0).getTextContent(), request.getTicketId()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/mock/MockApplicationEvent.java b/cas-server-core/src/test/java/org/jasig/cas/mock/MockApplicationEvent.java new file mode 100644 index 000000000000..0bf48d29e2a6 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/mock/MockApplicationEvent.java @@ -0,0 +1,35 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.mock; + +import org.springframework.context.ApplicationEvent; + +/** + * @author Marvin S. Addison + * @since 3.0.0 + */ +public class MockApplicationEvent extends ApplicationEvent { + + private static final long serialVersionUID = 3761968285092032567L; + + public MockApplicationEvent(final Object arg0) { + super(arg0); + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/mock/MockAuthenticationMetaDataPopulator.java b/cas-server-core/src/test/java/org/jasig/cas/mock/MockAuthenticationMetaDataPopulator.java new file mode 100644 index 000000000000..bf7133b8ec48 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/mock/MockAuthenticationMetaDataPopulator.java @@ -0,0 +1,38 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.mock; + +import org.jasig.cas.authentication.AuthenticationBuilder; +import org.jasig.cas.authentication.AuthenticationMetaDataPopulator; +import org.jasig.cas.authentication.Credential; + +/** + * @author Marvin S. Addison + * @since 3.0.0 + */ +public class MockAuthenticationMetaDataPopulator implements AuthenticationMetaDataPopulator { + + public void populateAttributes(final AuthenticationBuilder builder, final Credential credential) {} + + @Override + public boolean supports(final Credential credential) { + return true; + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/mock/MockService.java b/cas-server-core/src/test/java/org/jasig/cas/mock/MockService.java new file mode 100644 index 000000000000..b62ec1d8453f --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/mock/MockService.java @@ -0,0 +1,74 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.mock; + +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.DefaultResponse; +import org.jasig.cas.authentication.principal.Service; + +import java.util.Map; + +/** + * Simple mock implementation of a service principal. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +public class MockService implements Service { + + private static final long serialVersionUID = 117438127028057173L; + private boolean loggedOut; + private final String id; + + public MockService(final String id) { + this.id = id; + } + + public String getArtifactId() { + return null; + } + + public DefaultResponse getResponse(final String ticketId) { + return null; + } + + public boolean logOutOfService(final String sessionIdentifier) { + this.loggedOut = true; + return false; + } + + public boolean isLoggedOut() { + return this.loggedOut; + } + + public void setPrincipal(final Principal principal) {} + + public Map getAttributes() { + return null; + } + + public String getId() { + return id; + } + + public boolean matches(final Service service) { + return true; + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/mock/MockServiceTicket.java b/cas-server-core/src/test/java/org/jasig/cas/mock/MockServiceTicket.java new file mode 100644 index 000000000000..10cf6e16aa76 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/mock/MockServiceTicket.java @@ -0,0 +1,96 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.mock; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.UniqueTicketIdGenerator; + +import java.util.Date; + +/** + * Mock service ticket. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +public class MockServiceTicket implements ServiceTicket { + + private static final long serialVersionUID = 8203377063087967768L; + + private static final UniqueTicketIdGenerator ID_GENERATOR = new DefaultUniqueTicketIdGenerator(); + + private final String id; + + private final Date created; + + private final Service service; + + private final TicketGrantingTicket parent; + + public MockServiceTicket(final String id, final Service service, final TicketGrantingTicket parent) { + this.service = service; + this.id = id; + this.parent = parent; + created = new Date(); + } + + public Service getService() { + return service; + } + + public boolean isFromNewLogin() { + return false; + } + + public boolean isValidFor(final Service service) { + return this.service.equals(service); + } + + public TicketGrantingTicket grantTicketGrantingTicket( + final String id, + final Authentication authentication, + final ExpirationPolicy expirationPolicy) { + return null; + } + + public String getId() { + return id; + } + + public boolean isExpired() { + return false; + } + + public TicketGrantingTicket getGrantingTicket() { + return parent; + } + + public long getCreationTime() { + return created.getTime(); + } + + public int getCountOfUses() { + return 0; + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/mock/MockTicketGrantingTicket.java b/cas-server-core/src/test/java/org/jasig/cas/mock/MockTicketGrantingTicket.java new file mode 100644 index 000000000000..cffa6ba26655 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/mock/MockTicketGrantingTicket.java @@ -0,0 +1,162 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.mock; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.DefaultAuthenticationBuilder; +import org.jasig.cas.authentication.BasicCredentialMetaData; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.jasig.cas.authentication.CredentialMetaData; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.UniqueTicketIdGenerator; + +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Mock ticket-granting ticket. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +public class MockTicketGrantingTicket implements TicketGrantingTicket { + public static final UniqueTicketIdGenerator ID_GENERATOR = new DefaultUniqueTicketIdGenerator(); + + private static final long serialVersionUID = 6546995681334670659L; + + private final String id; + + private final Authentication authentication; + + private final Date created; + + private int usageCount; + + private boolean expired; + + private Service proxiedBy; + + private final Map services = new HashMap<>(); + + public MockTicketGrantingTicket(final String principal) { + id = ID_GENERATOR.getNewTicketId("TGT"); + final CredentialMetaData metaData = new BasicCredentialMetaData( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + authentication = new DefaultAuthenticationBuilder(new DefaultPrincipalFactory().createPrincipal(principal)) + .addCredential(metaData) + .addSuccess(SimpleTestUsernamePasswordAuthenticationHandler.class.getName(), + new DefaultHandlerResult(new SimpleTestUsernamePasswordAuthenticationHandler(), metaData)) + .build(); + + created = new Date(); + } + + @Override + public Authentication getAuthentication() { + return authentication; + } + + public ServiceTicket grantServiceTicket(final Service service) { + return grantServiceTicket(ID_GENERATOR.getNewTicketId("ST"), service, null, true); + } + + @Override + public ServiceTicket grantServiceTicket( + final String id, + final Service service, + final ExpirationPolicy expirationPolicy, + final boolean credentialsProvided) { + usageCount++; + return new MockServiceTicket(id, service, this); + } + + @Override + public Service getProxiedBy() { + return this.proxiedBy; + } + + @Override + public boolean isRoot() { + return true; + } + + @Override + public TicketGrantingTicket getRoot() { + return this; + } + + @Override + public List getSupplementalAuthentications() { + return Collections.emptyList(); + } + + @Override + public List getChainedAuthentications() { + return Collections.emptyList(); + } + + @Override + public String getId() { + return id; + } + + @Override + public boolean isExpired() { + return expired; + } + + @Override + public TicketGrantingTicket getGrantingTicket() { + return this; + } + + @Override + public long getCreationTime() { + return created.getTime(); + } + + @Override + public int getCountOfUses() { + return usageCount; + } + + @Override + public Map getServices() { + return this.services; + } + + @Override + public void removeAllServices() { + } + + @Override + public void markTicketExpired() { + expired = true; + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/mock/MockValidationSpecification.java b/cas-server-core/src/test/java/org/jasig/cas/mock/MockValidationSpecification.java new file mode 100644 index 000000000000..ef3be6cca5ed --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/mock/MockValidationSpecification.java @@ -0,0 +1,42 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.mock; + +import org.jasig.cas.validation.Assertion; +import org.jasig.cas.validation.ValidationSpecification; + +/** + * Class to test the Runtime exception thrown when there is no default + * constructor on a ValidationSpecification. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public class MockValidationSpecification implements ValidationSpecification { + + private final boolean test; + + public MockValidationSpecification(final boolean test) { + this.test = test; + } + + public boolean isSatisfiedBy(final Assertion assertion) { + return this.test; + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/monitor/AbstractCacheMonitorTests.java b/cas-server-core/src/test/java/org/jasig/cas/monitor/AbstractCacheMonitorTests.java new file mode 100644 index 000000000000..15a6bce187ff --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/monitor/AbstractCacheMonitorTests.java @@ -0,0 +1,76 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * Unit test for {@link AbstractCacheMonitor}. + * + * @author Marvin S. Addison + * @since 3.5.1 + */ +public class AbstractCacheMonitorTests { + @Test + public void verifyObserveOk() throws Exception { + final AbstractCacheMonitor monitor = new AbstractCacheMonitor() { + protected SimpleCacheStatistics[] getStatistics() { + return statsArray(new SimpleCacheStatistics(100, 200, 0)); + } + }; + assertEquals(StatusCode.OK, monitor.observe().getCode()); + } + + @Test + public void verifyObserveWarn() throws Exception { + final AbstractCacheMonitor monitor = new AbstractCacheMonitor() { + protected SimpleCacheStatistics[] getStatistics() { + return statsArray(new SimpleCacheStatistics(199, 200, 0)); + } + }; + assertEquals(StatusCode.WARN, monitor.observe().getCode()); + } + + @Test + public void verifyObserveError() throws Exception { + final AbstractCacheMonitor monitor = new AbstractCacheMonitor() { + protected SimpleCacheStatistics[] getStatistics() { + return statsArray(new SimpleCacheStatistics(100, 200, 1)); + } + }; + assertEquals(StatusCode.WARN, monitor.observe().getCode()); + } + + + @Test + public void verifyObserveError2() throws Exception { + // When cache has exceeded both thresholds, should report ERROR status + final AbstractCacheMonitor monitor = new AbstractCacheMonitor() { + protected SimpleCacheStatistics[] getStatistics() { + return statsArray(new SimpleCacheStatistics(199, 200, 1)); + } + }; + assertEquals(StatusCode.WARN, monitor.observe().getCode()); + } + + protected static SimpleCacheStatistics[] statsArray(final SimpleCacheStatistics... statistics) { + return statistics; + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/monitor/AbstractPoolMonitorTests.java b/cas-server-core/src/test/java/org/jasig/cas/monitor/AbstractPoolMonitorTests.java new file mode 100644 index 000000000000..08698a9e2ebf --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/monitor/AbstractPoolMonitorTests.java @@ -0,0 +1,109 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Unit test for {@link AbstractPoolMonitor} class. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +public class AbstractPoolMonitorTests { + + private final ExecutorService executor = Executors.newSingleThreadExecutor(); + + @Test + public void verifyObserveOK() throws Exception { + final AbstractPoolMonitor monitor = new AbstractPoolMonitor() { + protected StatusCode checkPool() throws Exception { + return StatusCode.OK; + } + + protected int getIdleCount() { + return 3; + } + + protected int getActiveCount() { + return 2; + } + }; + monitor.setExecutor(this.executor); + monitor.setMaxWait(1000); + final PoolStatus status = monitor.observe(); + assertEquals(StatusCode.OK, status.getCode()); + assertEquals(3, status.getIdleCount()); + assertEquals(2, status.getActiveCount()); + } + + + @Test + public void verifyObserveWarn() throws Exception { + final AbstractPoolMonitor monitor = new AbstractPoolMonitor() { + protected StatusCode checkPool() throws Exception { + Thread.sleep(1000); + return StatusCode.OK; + } + + protected int getIdleCount() { + return 1; + } + + protected int getActiveCount() { + return 1; + } + }; + monitor.setExecutor(this.executor); + monitor.setMaxWait(500); + final PoolStatus status = monitor.observe(); + assertEquals(StatusCode.WARN, status.getCode()); + assertEquals(1, status.getIdleCount()); + assertEquals(1, status.getActiveCount()); + } + + + @Test + public void verifyObserveError() throws Exception { + final AbstractPoolMonitor monitor = new AbstractPoolMonitor() { + protected StatusCode checkPool() throws Exception { + throw new RuntimeException("Pool check failed due to rogue penguins."); + } + + protected int getIdleCount() { + return 1; + } + + protected int getActiveCount() { + return 1; + } + }; + monitor.setExecutor(this.executor); + monitor.setMaxWait(500); + final PoolStatus status = monitor.observe(); + assertEquals(StatusCode.ERROR, status.getCode()); + assertEquals(1, status.getIdleCount()); + assertEquals(1, status.getActiveCount()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/monitor/HealthCheckMonitorTests.java b/cas-server-core/src/test/java/org/jasig/cas/monitor/HealthCheckMonitorTests.java new file mode 100644 index 000000000000..debba2e4a235 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/monitor/HealthCheckMonitorTests.java @@ -0,0 +1,93 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.jasig.cas.ticket.registry.DefaultTicketRegistry; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Unit test for {@link HealthCheckMonitor} class. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +public class HealthCheckMonitorTests { + + private HealthCheckMonitor monitor; + + @Before + public void setUp() throws Exception { + this.monitor = new HealthCheckMonitor(); + } + + @Test + public void verifyObserveUnknown() throws Exception { + assertEquals(StatusCode.UNKNOWN, this.monitor.observe().getCode()); + } + + @Test + public void verifyObserveOk() throws Exception { + final Set monitors = new HashSet<>(); + monitors.add(new MemoryMonitor()); + monitors.add(newSessionMonitor()); + this.monitor.setMonitors(monitors); + assertEquals(StatusCode.OK, this.monitor.observe().getCode()); + } + + @Test + public void verifyObserveWarn() throws Exception { + final Set monitors = new HashSet<>(); + final MemoryMonitor memoryMonitor = new MemoryMonitor(); + memoryMonitor.setFreeMemoryWarnThreshold(100); + monitors.add(memoryMonitor); + monitors.add(newSessionMonitor()); + this.monitor.setMonitors(monitors); + assertEquals(StatusCode.WARN, this.monitor.observe().getCode()); + } + + @Test + public void verifyThrowsUncheckedException() throws Exception { + final Monitor throwsUnchecked = new Monitor() { + @Override + public String getName() { + return "ThrowsUnchecked"; + } + + @Override + public Status observe() { + throw new IllegalStateException("Boogity!"); + } + }; + this.monitor.setMonitors(Collections.singleton(throwsUnchecked)); + assertEquals(StatusCode.ERROR, this.monitor.observe().getCode()); + } + + private SessionMonitor newSessionMonitor() { + final SessionMonitor sessionMonitor = new SessionMonitor(); + sessionMonitor.setTicketRegistry(new DefaultTicketRegistry()); + return sessionMonitor; + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/monitor/MemoryMonitorTests.java b/cas-server-core/src/test/java/org/jasig/cas/monitor/MemoryMonitorTests.java new file mode 100644 index 000000000000..35b322c9758f --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/monitor/MemoryMonitorTests.java @@ -0,0 +1,44 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Unit test for {@link MemoryMonitor} class. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +public class MemoryMonitorTests { + + @Test + public void verifyObserveOk() throws Exception { + assertEquals(StatusCode.OK, new MemoryMonitor().observe().getCode()); + } + + @Test + public void verifyObserveWarn() throws Exception { + final MemoryMonitor monitor = new MemoryMonitor(); + monitor.setFreeMemoryWarnThreshold(100); + assertEquals(StatusCode.WARN, monitor.observe().getCode()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/monitor/SessionMonitorTests.java b/cas-server-core/src/test/java/org/jasig/cas/monitor/SessionMonitorTests.java new file mode 100644 index 000000000000..ab361a1f9578 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/monitor/SessionMonitorTests.java @@ -0,0 +1,103 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.mock.MockService; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.jasig.cas.ticket.registry.DefaultTicketRegistry; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.jasig.cas.ticket.support.HardTimeoutExpirationPolicy; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Unit test for {@link SessionMonitor} class. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +public class SessionMonitorTests { + + private static final ExpirationPolicy TEST_EXP_POLICY = new HardTimeoutExpirationPolicy(10000); + private static final UniqueTicketIdGenerator GENERATOR = new DefaultUniqueTicketIdGenerator(); + + private DefaultTicketRegistry defaultRegistry; + private SessionMonitor monitor; + + @Before + public void setUp() { + this.defaultRegistry = new DefaultTicketRegistry(); + this.monitor = new SessionMonitor(); + this.monitor.setTicketRegistry(this.defaultRegistry); + } + + @Test + public void verifyObserveOk() throws Exception { + addTicketsToRegistry(this.defaultRegistry, 5, 10); + final SessionStatus status = this.monitor.observe(); + assertEquals(5, status.getSessionCount()); + assertEquals(10, status.getServiceTicketCount()); + assertEquals(StatusCode.OK, status.getCode()); + } + + @Test + public void verifyObserveWarnSessionsExceeded() throws Exception { + addTicketsToRegistry(this.defaultRegistry, 10, 1); + this.monitor.setSessionCountWarnThreshold(5); + final SessionStatus status = this.monitor.observe(); + assertEquals(StatusCode.WARN, status.getCode()); + assertTrue(status.getDescription().contains("Session count")); + } + + @Test + public void verifyObserveWarnServiceTicketsExceeded() throws Exception { + addTicketsToRegistry(this.defaultRegistry, 1, 10); + this.monitor.setServiceTicketCountWarnThreshold(5); + final SessionStatus status = this.monitor.observe(); + assertEquals(StatusCode.WARN, status.getCode()); + assertTrue(status.getDescription().contains("Service ticket count")); + } + private void addTicketsToRegistry(final TicketRegistry registry, final int tgtCount, final int stCount) { + TicketGrantingTicketImpl ticket = null; + for (int i = 0; i < tgtCount; i++) { + ticket = new TicketGrantingTicketImpl( + GENERATOR.getNewTicketId("TGT"), + TestUtils.getAuthentication(), + TEST_EXP_POLICY); + registry.addTicket(ticket); + } + + if (ticket != null) { + for (int i = 0; i < stCount; i++) { + registry.addTicket(ticket.grantServiceTicket( + GENERATOR.getNewTicketId("ST"), + new MockService("junit"), + TEST_EXP_POLICY, + false)); + } + } + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/remoting/server/RemoteCentralAuthenticationServiceTests.java b/cas-server-core/src/test/java/org/jasig/cas/remoting/server/RemoteCentralAuthenticationServiceTests.java new file mode 100644 index 000000000000..f68cbde1a959 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/remoting/server/RemoteCentralAuthenticationServiceTests.java @@ -0,0 +1,161 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.remoting.server; + +import org.jasig.cas.AbstractCentralAuthenticationServiceTest; +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.AuthenticationException; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.fail; + +/** + * @author Scott Battaglia + @deprecated As of 4.1 + * @since 3.0.0 + */ +@Deprecated +public class RemoteCentralAuthenticationServiceTests extends AbstractCentralAuthenticationServiceTest { + + private RemoteCentralAuthenticationService remoteCentralAuthenticationService; + + @Before + public void onSetUp() throws Exception { + this.remoteCentralAuthenticationService = new RemoteCentralAuthenticationService(); + this.remoteCentralAuthenticationService.setCentralAuthenticationService(getCentralAuthenticationService()); + } + + @Test + public void verifyValidCredentials() throws Exception { + this.remoteCentralAuthenticationService.createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + } + + @Test + public void verifyInvalidCredentials() throws Exception { + try { + this.remoteCentralAuthenticationService.createTicketGrantingTicket( + TestUtils.getCredentialsWithDifferentUsernameAndPassword(null, null)); + fail("IllegalArgumentException expected."); + } catch (final IllegalArgumentException e) { + return; + } + } + + @Test + public void verifyDontUseValidatorsToCheckValidCredentials() throws Exception { + try { + this.remoteCentralAuthenticationService.createTicketGrantingTicket( + TestUtils.getCredentialsWithDifferentUsernameAndPassword()); + fail("AuthenticationException expected."); + } catch (final AuthenticationException e) { + return; + } + } + + @Test + public void verifyDestroyTicketGrantingTicket() { + this.remoteCentralAuthenticationService + .destroyTicketGrantingTicket("test"); + } + + @Test + public void verifyGrantServiceTicketWithValidTicketGrantingTicket() throws Exception { + final TicketGrantingTicket ticketId = this.remoteCentralAuthenticationService + .createTicketGrantingTicket(TestUtils + .getCredentialsWithSameUsernameAndPassword()); + this.remoteCentralAuthenticationService.grantServiceTicket(ticketId.getId(), + TestUtils.getService()); + } + + @Test + public void verifyGrantServiceTicketWithValidCredentials() throws Exception { + final TicketGrantingTicket ticketGrantingTicketId = this.remoteCentralAuthenticationService + .createTicketGrantingTicket(TestUtils + .getCredentialsWithSameUsernameAndPassword()); + this.remoteCentralAuthenticationService.grantServiceTicket( + ticketGrantingTicketId.getId(), TestUtils.getService(), TestUtils + .getCredentialsWithSameUsernameAndPassword()); + } + + @Test + public void verifyGrantServiceTicketWithNullCredentials() throws Exception { + final TicketGrantingTicket ticketGrantingTicketId = this.remoteCentralAuthenticationService + .createTicketGrantingTicket(TestUtils + .getCredentialsWithSameUsernameAndPassword()); + this.remoteCentralAuthenticationService.grantServiceTicket( + ticketGrantingTicketId.getId(), TestUtils.getService(), (Credential[]) null); + } + + @Test + public void verifyGrantServiceTicketWithEmptyCredentials() throws Exception { + final TicketGrantingTicket ticketGrantingTicketId = this.remoteCentralAuthenticationService + .createTicketGrantingTicket(TestUtils + .getCredentialsWithSameUsernameAndPassword()); + try { + this.remoteCentralAuthenticationService.grantServiceTicket( + ticketGrantingTicketId.getId(), TestUtils.getService(), TestUtils + .getCredentialsWithDifferentUsernameAndPassword("", "")); + fail("IllegalArgumentException expected."); + } catch (final IllegalArgumentException e) { + return; + } + } + + @Test + public void verifyValidateServiceTicketWithValidService() throws Exception { + final TicketGrantingTicket ticketGrantingTicket = this.remoteCentralAuthenticationService + .createTicketGrantingTicket(TestUtils + .getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket serviceTicket = this.remoteCentralAuthenticationService + .grantServiceTicket(ticketGrantingTicket.getId(), TestUtils.getService()); + + this.remoteCentralAuthenticationService.validateServiceTicket( + serviceTicket.getId(), TestUtils.getService()); + } + + @Test + public void verifyDelegateTicketGrantingTicketWithValidCredentials() throws Exception { + final TicketGrantingTicket ticketGrantingTicket = this.remoteCentralAuthenticationService + .createTicketGrantingTicket(TestUtils + .getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket serviceTicket = this.remoteCentralAuthenticationService + .grantServiceTicket(ticketGrantingTicket.getId(), TestUtils.getService()); + this.remoteCentralAuthenticationService.delegateTicketGrantingTicket( + serviceTicket.getId(), TestUtils.getHttpBasedServiceCredentials()); + } + + @Test(expected=IllegalArgumentException.class) + public void verifyDelegateTicketGrantingTicketWithInvalidCredentials() throws Exception { + final TicketGrantingTicket ticketGrantingTicket = this.remoteCentralAuthenticationService + .createTicketGrantingTicket(TestUtils + .getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket serviceTicket = this.remoteCentralAuthenticationService + .grantServiceTicket(ticketGrantingTicket.getId(), TestUtils.getService()); + + this.remoteCentralAuthenticationService + .delegateTicketGrantingTicket(serviceTicket.getId(), TestUtils + .getCredentialsWithDifferentUsernameAndPassword("", "")); + fail("IllegalArgumentException expected."); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/AbstractRegisteredServiceTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/AbstractRegisteredServiceTests.java new file mode 100644 index 000000000000..6ad1e7286827 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/AbstractRegisteredServiceTests.java @@ -0,0 +1,177 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.authentication.principal.ShibbolethCompatiblePersistentIdGenerator; +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Unit test for {@link AbstractRegisteredService}. + * + * @author Marvin S. Addison + * @since 3.4.12 + */ +public class AbstractRegisteredServiceTests { + + private static final long ID = 1000; + private static final String DESCRIPTION = "test"; + private static final String SERVICEID = "serviceId"; + private static final String THEME = "theme"; + private static final String NAME = "name"; + private static final boolean ENABLED = false; + private static final boolean ALLOWED_TO_PROXY = false; + private static final boolean SSO_ENABLED = false; + + private final AbstractRegisteredService r = new AbstractRegisteredService() { + private static final long serialVersionUID = 1L; + + public void setServiceId(final String id) { + serviceId = id; + } + + protected AbstractRegisteredService newInstance() { + return this; + } + + public boolean matches(final Service service) { + return true; + } + }; + + @Test + public void verifyAllowToProxyIsFalseByDefault() { + final RegexRegisteredService regexRegisteredService = new RegexRegisteredService(); + assertFalse(regexRegisteredService.getProxyPolicy().isAllowedToProxy()); + final RegisteredServiceImpl registeredServiceImpl = new RegisteredServiceImpl(); + assertFalse(registeredServiceImpl.getProxyPolicy().isAllowedToProxy()); + } + + @Test + public void verifySettersAndGetters() { + prepareService(); + + assertEquals(ALLOWED_TO_PROXY, this.r.getProxyPolicy().isAllowedToProxy()); + assertEquals(DESCRIPTION, this.r.getDescription()); + assertEquals(ENABLED, this.r.getAccessStrategy() + .isServiceAccessAllowed()); + assertEquals(ID, this.r.getId()); + assertEquals(NAME, this.r.getName()); + assertEquals(SERVICEID, this.r.getServiceId()); + assertEquals(SSO_ENABLED, this.r.getAccessStrategy() + .isServiceAccessAllowedForSso()); + assertEquals(THEME, this.r.getTheme()); + + assertFalse(this.r.equals(null)); + assertFalse(this.r.equals(new Object())); + assertTrue(this.r.equals(this.r)); + } + + @Test + public void verifyEquals() throws Exception { + assertTrue(r.equals(r.clone())); + assertFalse(new RegisteredServiceImpl().equals(null)); + assertFalse(new RegisteredServiceImpl().equals(new Object())); + } + + private void prepareService() { + this.r.setUsernameAttributeProvider( + new AnonymousRegisteredServiceUsernameAttributeProvider( + new ShibbolethCompatiblePersistentIdGenerator("casrox"))); + this.r.setDescription(DESCRIPTION); + this.r.setId(ID); + this.r.setName(NAME); + this.r.setServiceId(SERVICEID); + this.r.setTheme(THEME); + this.r.setAccessStrategy(new DefaultRegisteredServiceAccessStrategy(ENABLED, SSO_ENABLED)); + } + + @Test + public void verifyServiceAttributeFilterAllAttributes() { + prepareService(); + this.r.setAttributeReleasePolicy(new ReturnAllAttributeReleasePolicy()); + final Principal p = mock(Principal.class); + + final Map map = new HashMap<>(); + map.put("attr1", "value1"); + map.put("attr2", "value2"); + map.put("attr3", Arrays.asList("v3", "v4")); + + when(p.getAttributes()).thenReturn(map); + when(p.getId()).thenReturn("principalId"); + + final Map attr = this.r.getAttributeReleasePolicy().getAttributes(p); + assertEquals(attr.size(), map.size()); + } + + @Test + public void verifyServiceAttributeFilterAllowedAttributes() { + prepareService(); + final ReturnAllowedAttributeReleasePolicy policy = new ReturnAllowedAttributeReleasePolicy(); + policy.setAllowedAttributes(Arrays.asList("attr1", "attr3")); + this.r.setAttributeReleasePolicy(policy); + final Principal p = mock(Principal.class); + + final Map map = new HashMap<>(); + map.put("attr1", "value1"); + map.put("attr2", "value2"); + map.put("attr3", Arrays.asList("v3", "v4")); + + when(p.getAttributes()).thenReturn(map); + when(p.getId()).thenReturn("principalId"); + + final Map attr = this.r.getAttributeReleasePolicy().getAttributes(p); + assertEquals(attr.size(), 2); + assertTrue(attr.containsKey("attr1")); + assertTrue(attr.containsKey("attr3")); + } + + @Test + public void verifyServiceAttributeFilterMappedAttributes() { + prepareService(); + final ReturnMappedAttributeReleasePolicy policy = new ReturnMappedAttributeReleasePolicy(); + final Map mappedAttr = new HashMap<>(); + mappedAttr.put("attr1", "newAttr1"); + + policy.setAllowedAttributes(mappedAttr); + + this.r.setAttributeReleasePolicy(policy); + final Principal p = mock(Principal.class); + + final Map map = new HashMap<>(); + map.put("attr1", "value1"); + map.put("attr2", "value2"); + map.put("attr3", Arrays.asList("v3", "v4")); + + when(p.getAttributes()).thenReturn(map); + when(p.getId()).thenReturn("principalId"); + + final Map attr = this.r.getAttributeReleasePolicy().getAttributes(p); + assertEquals(attr.size(), 1); + assertTrue(attr.containsKey("newAttr1")); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/AnonymousRegisteredServiceUsernameAttributeProviderTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/AnonymousRegisteredServiceUsernameAttributeProviderTests.java new file mode 100644 index 000000000000..27695b697e41 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/AnonymousRegisteredServiceUsernameAttributeProviderTests.java @@ -0,0 +1,61 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.authentication.principal.ShibbolethCompatiblePersistentIdGenerator; +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class AnonymousRegisteredServiceUsernameAttributeProviderTests { + + @Test + public void verifyPrincipalResolution() { + final AnonymousRegisteredServiceUsernameAttributeProvider provider = + new AnonymousRegisteredServiceUsernameAttributeProvider( + new ShibbolethCompatiblePersistentIdGenerator("casrox")); + + final Service service = mock(Service.class); + when(service.getId()).thenReturn("id"); + final Principal principal = new DefaultPrincipalFactory().createPrincipal("uid"); + final String id = provider.resolveUsername(principal, service); + assertNotNull(id); + } + + @Test + public void verifyEquality() { + final AnonymousRegisteredServiceUsernameAttributeProvider provider = + new AnonymousRegisteredServiceUsernameAttributeProvider( + new ShibbolethCompatiblePersistentIdGenerator("casrox")); + + final AnonymousRegisteredServiceUsernameAttributeProvider provider2 = + new AnonymousRegisteredServiceUsernameAttributeProvider( + new ShibbolethCompatiblePersistentIdGenerator("casrox")); + + assertEquals(provider, provider2); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/AttributeReleasePolicyTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/AttributeReleasePolicyTests.java new file mode 100644 index 000000000000..d09bdf9aff47 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/AttributeReleasePolicyTests.java @@ -0,0 +1,173 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.apache.commons.lang3.SerializationUtils; +import org.jasig.cas.authentication.principal.CachingPrincipalAttributesRepository; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.PrincipalAttributesRepository; +import org.jasig.cas.services.support.RegisteredServiceRegexAttributeFilter; +import org.jasig.services.persondir.IPersonAttributeDao; +import org.jasig.services.persondir.IPersonAttributes; +import org.jasig.services.persondir.support.StubPersonAttributeDao; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Attribute filtering policy tests. + * @author Misagh Moayyed + * @since 4.0.0 + */ +public class AttributeReleasePolicyTests { + @Test + public void verifyAttributeFilterMappedAttributes() { + final ReturnMappedAttributeReleasePolicy policy = new ReturnMappedAttributeReleasePolicy(); + final Map mappedAttr = new HashMap<>(); + mappedAttr.put("attr1", "newAttr1"); + + policy.setAllowedAttributes(mappedAttr); + + final Principal p = mock(Principal.class); + + final Map map = new HashMap<>(); + map.put("attr1", "value1"); + map.put("attr2", "value2"); + map.put("attr3", Arrays.asList("v3", "v4")); + + when(p.getAttributes()).thenReturn(map); + when(p.getId()).thenReturn("principalId"); + + final Map attr = policy.getAttributes(p); + assertEquals(attr.size(), 1); + assertTrue(attr.containsKey("newAttr1")); + + final byte[] data = SerializationUtils.serialize(policy); + final ReturnMappedAttributeReleasePolicy p2 = SerializationUtils.deserialize(data); + assertNotNull(p2); + assertEquals(p2.getAllowedAttributes(), policy.getAllowedAttributes()); + } + + @Test + public void verifyServiceAttributeFilterAllowedAttributes() { + final ReturnAllowedAttributeReleasePolicy policy = new ReturnAllowedAttributeReleasePolicy(); + policy.setAllowedAttributes(Arrays.asList("attr1", "attr3")); + final Principal p = mock(Principal.class); + + final Map map = new HashMap<>(); + map.put("attr1", "value1"); + map.put("attr2", "value2"); + map.put("attr3", Arrays.asList("v3", "v4")); + + when(p.getAttributes()).thenReturn(map); + when(p.getId()).thenReturn("principalId"); + + final Map attr = policy.getAttributes(p); + assertEquals(attr.size(), 2); + assertTrue(attr.containsKey("attr1")); + assertTrue(attr.containsKey("attr3")); + + final byte[] data = SerializationUtils.serialize(policy); + final ReturnAllowedAttributeReleasePolicy p2 = SerializationUtils.deserialize(data); + assertNotNull(p2); + assertEquals(p2.getAllowedAttributes(), policy.getAllowedAttributes()); + } + + @Test + public void verifyServiceAttributeFilterAllowedAttributesWithARegexFilter() { + final ReturnAllowedAttributeReleasePolicy policy = new ReturnAllowedAttributeReleasePolicy(); + policy.setAllowedAttributes(Arrays.asList("attr1", "attr3", "another")); + policy.setAttributeFilter(new RegisteredServiceRegexAttributeFilter("v3")); + final Principal p = mock(Principal.class); + + final Map map = new HashMap<>(); + map.put("attr1", "value1"); + map.put("attr2", "value2"); + map.put("attr3", Arrays.asList("v3", "v4")); + + when(p.getAttributes()).thenReturn(map); + when(p.getId()).thenReturn("principalId"); + + final Map attr = policy.getAttributes(p); + assertEquals(attr.size(), 1); + assertTrue(attr.containsKey("attr3")); + + final byte[] data = SerializationUtils.serialize(policy); + final ReturnAllowedAttributeReleasePolicy p2 = SerializationUtils.deserialize(data); + assertNotNull(p2); + assertEquals(p2.getAllowedAttributes(), policy.getAllowedAttributes()); + assertEquals(p2.getAttributeFilter(), policy.getAttributeFilter()); + } + + @Test + public void verifyServiceAttributeFilterAllAttributes() { + final ReturnAllAttributeReleasePolicy policy = new ReturnAllAttributeReleasePolicy(); + final Principal p = mock(Principal.class); + + final Map map = new HashMap<>(); + map.put("attr1", "value1"); + map.put("attr2", "value2"); + map.put("attr3", Arrays.asList("v3", "v4")); + + when(p.getAttributes()).thenReturn(map); + when(p.getId()).thenReturn("principalId"); + + final Map attr = policy.getAttributes(p); + assertEquals(attr.size(), map.size()); + + final byte[] data = SerializationUtils.serialize(policy); + final ReturnAllAttributeReleasePolicy p2 = SerializationUtils.deserialize(data); + assertNotNull(p2); + } + + @Test + public void checkServiceAttributeFilterAllAttributesWithCachingTurnedOn() { + final ReturnAllAttributeReleasePolicy policy = new ReturnAllAttributeReleasePolicy(); + + final Map> attributes = new HashMap<>(); + attributes.put("values", Arrays.asList(new Object[]{"v1", "v2", "v3"})); + attributes.put("cn", Arrays.asList(new Object[]{"commonName"})); + attributes.put("username", Arrays.asList(new Object[]{"uid"})); + + final IPersonAttributeDao dao = new StubPersonAttributeDao(attributes); + final IPersonAttributes person = mock(IPersonAttributes.class); + when(person.getName()).thenReturn("uid"); + when(person.getAttributes()).thenReturn(attributes); + + final PrincipalAttributesRepository repository = + new CachingPrincipalAttributesRepository(dao, TimeUnit.MILLISECONDS, 100); + + final Principal p = new DefaultPrincipalFactory().createPrincipal("uid", + Collections.singletonMap("mail", "final@example.com")); + + policy.setPrincipalAttributesRepository(repository); + + final Map attr = policy.getAttributes(p); + assertEquals(attr.size(), attributes.size()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/DefaultRegisteredServiceAccessStrategyTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/DefaultRegisteredServiceAccessStrategyTests.java new file mode 100644 index 000000000000..c467d7760703 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/DefaultRegisteredServiceAccessStrategyTests.java @@ -0,0 +1,164 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.services; + +import com.google.common.collect.Sets; +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.*; + +/** + * This is test cases for + * {@link DefaultRegisteredServiceAccessStrategy}. + * + * @author Misagh Moayyed mmoayyed@unicon.net + * @since 4.1 + */ +public class DefaultRegisteredServiceAccessStrategyTests { + @Test + public void checkDefaultAuthzStrategyConfig() { + final RegisteredServiceAccessStrategy authz = + new DefaultRegisteredServiceAccessStrategy(); + assertTrue(authz.isServiceAccessAllowed()); + assertTrue(authz.isServiceAccessAllowedForSso()); + } + + @Test + public void checkDisabledAuthzStrategyConfig() { + final RegisteredServiceAccessStrategy authz = + new DefaultRegisteredServiceAccessStrategy(false, true); + assertFalse(authz.isServiceAccessAllowed()); + assertTrue(authz.isServiceAccessAllowedForSso()); + } + + @Test + public void checkDisabledSsoAuthzStrategyConfig() { + final RegisteredServiceAccessStrategy authz = + new DefaultRegisteredServiceAccessStrategy(true, false); + assertTrue(authz.isServiceAccessAllowed()); + assertFalse(authz.isServiceAccessAllowedForSso()); + } + + @Test + public void setAuthzStrategyConfig() { + final DefaultRegisteredServiceAccessStrategy authz = + new DefaultRegisteredServiceAccessStrategy(false, false); + authz.setEnabled(true); + authz.setSsoEnabled(true); + assertTrue(authz.isServiceAccessAllowed()); + assertTrue(authz.isServiceAccessAllowedForSso()); + assertTrue(authz.isRequireAllAttributes()); + } + + @Test + public void checkAuthzPrincipalNoAttrRequirements() { + final DefaultRegisteredServiceAccessStrategy authz = + new DefaultRegisteredServiceAccessStrategy(); + assertTrue(authz.doPrincipalAttributesAllowServiceAccess(new HashMap())); + } + + @Test + public void checkAuthzPrincipalWithAttrRequirementsEmptyPrincipal() { + final DefaultRegisteredServiceAccessStrategy authz = + new DefaultRegisteredServiceAccessStrategy(); + authz.setRequiredAttributes(this.getRequiredAttributes()); + assertFalse(authz.doPrincipalAttributesAllowServiceAccess(new HashMap())); + } + + @Test + public void checkAuthzPrincipalWithAttrRequirementsAll() { + final DefaultRegisteredServiceAccessStrategy authz = + new DefaultRegisteredServiceAccessStrategy(); + authz.setRequiredAttributes(this.getRequiredAttributes()); + assertTrue(authz.doPrincipalAttributesAllowServiceAccess( + this.getPrincipalAttributes())); + } + + @Test + public void checkAuthzPrincipalWithAttrRequirementsMissingOne() { + final DefaultRegisteredServiceAccessStrategy authz = + new DefaultRegisteredServiceAccessStrategy(); + authz.setRequiredAttributes(this.getRequiredAttributes()); + + final Map pAttrs = this.getPrincipalAttributes(); + pAttrs.remove("cn"); + + assertFalse(authz.doPrincipalAttributesAllowServiceAccess(pAttrs)); + } + + @Test + public void checkAuthzPrincipalWithAttrRequirementsMissingOneButNotAllNeeded() { + final DefaultRegisteredServiceAccessStrategy authz = + new DefaultRegisteredServiceAccessStrategy(); + authz.setRequiredAttributes(this.getRequiredAttributes()); + authz.setRequireAllAttributes(false); + final Map pAttrs = this.getPrincipalAttributes(); + pAttrs.remove("cn"); + + assertTrue(authz.doPrincipalAttributesAllowServiceAccess(pAttrs)); + } + + @Test + public void checkAuthzPrincipalWithAttrRequirementsNoValueMatch() { + final DefaultRegisteredServiceAccessStrategy authz = + new DefaultRegisteredServiceAccessStrategy(); + authz.setRequiredAttributes(this.getRequiredAttributes()); + authz.setRequireAllAttributes(false); + final Map pAttrs = this.getPrincipalAttributes(); + pAttrs.remove("cn"); + pAttrs.put("givenName", "theName"); + assertFalse(authz.doPrincipalAttributesAllowServiceAccess(pAttrs)); + } + + @Test + public void checkAuthzPrincipalWithAttrValueCaseSensitiveComparison() { + final DefaultRegisteredServiceAccessStrategy authz = + new DefaultRegisteredServiceAccessStrategy(); + authz.setRequiredAttributes(this.getRequiredAttributes()); + final Map pAttrs = this.getPrincipalAttributes(); + pAttrs.put("cn", "CAS"); + pAttrs.put("givenName", "kaz"); + assertFalse(authz.doPrincipalAttributesAllowServiceAccess(pAttrs)); + } + + + private Map> getRequiredAttributes() { + final Map> map = new HashMap<>(); + map.put("cn", Sets.newHashSet("cas", "SSO")); + map.put("givenName", Sets.newHashSet("CAS", "KAZ")); + return map; + } + + private Map getPrincipalAttributes() { + final Map map = new HashMap<>(); + map.put("cn", "cas"); + map.put("givenName", Arrays.asList("cas", "KAZ")); + map.put("sn", "surname"); + map.put("phone", "123-456-7890"); + + return map; + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/DefaultRegisteredServiceUsernameProviderTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/DefaultRegisteredServiceUsernameProviderTests.java new file mode 100644 index 000000000000..81731ac411a2 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/DefaultRegisteredServiceUsernameProviderTests.java @@ -0,0 +1,55 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.principal.Principal; +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class DefaultRegisteredServiceUsernameProviderTests { + + @Test + public void verifyRegServiceUsername() { + final DefaultRegisteredServiceUsernameProvider provider = + new DefaultRegisteredServiceUsernameProvider(); + + final Principal principal = mock(Principal.class); + when(principal.getId()).thenReturn("id"); + final String id = provider.resolveUsername(principal, TestUtils.getService()); + assertEquals(id, principal.getId()); + } + + @Test + public void verifyEquality() { + final DefaultRegisteredServiceUsernameProvider provider = + new DefaultRegisteredServiceUsernameProvider(); + + final DefaultRegisteredServiceUsernameProvider provider2 = + new DefaultRegisteredServiceUsernameProvider(); + + assertEquals(provider, provider2); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/DefaultServicesManagerImplTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/DefaultServicesManagerImplTests.java new file mode 100644 index 000000000000..38815840c028 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/DefaultServicesManagerImplTests.java @@ -0,0 +1,243 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.Service; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; + +/** + * + * @author battags + * @since 3.0.0 + * + */ +public class DefaultServicesManagerImplTests { + + private DefaultServicesManagerImpl defaultServicesManagerImpl; + + @Before + public void setUp() throws Exception { + final InMemoryServiceRegistryDaoImpl dao = new InMemoryServiceRegistryDaoImpl(); + final List list = new ArrayList<>(); + + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setId(2500); + r.setServiceId("serviceId"); + r.setName("serviceName"); + r.setEvaluationOrder(1000); + + list.add(r); + + dao.setRegisteredServices(list); + this.defaultServicesManagerImpl = new DefaultServicesManagerImpl(dao); + } + + @Test + public void verifySaveAndGet() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setId(1000); + r.setName("test"); + r.setServiceId("test"); + + this.defaultServicesManagerImpl.save(r); + assertNotNull(this.defaultServicesManagerImpl.findServiceBy(1000)); + } + + @Test + public void verifyMultiServicesBySameName() { + RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setId(666); + r.setName("testServiceName"); + r.setServiceId("testServiceA"); + + this.defaultServicesManagerImpl.save(r); + + r = new RegisteredServiceImpl(); + r.setId(999); + r.setName("testServiceName"); + r.setServiceId("testServiceB"); + + this.defaultServicesManagerImpl.save(r); + + /** Added 2 above, plus another that is added during @Setup **/ + assertEquals(3, this.defaultServicesManagerImpl.getAllServices().size()); + } + + @Test + public void verifySaveWithReturnedPersistedInstance() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setId(1000L); + r.setName("test"); + r.setServiceId("test"); + + final RegisteredService persistedRs = this.defaultServicesManagerImpl.save(r); + assertNotNull(persistedRs); + assertEquals(1000L, persistedRs.getId()); + } + + @Test + public void verifyDeleteAndGet() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setId(1000); + r.setName("test"); + r.setServiceId("test"); + + this.defaultServicesManagerImpl.save(r); + assertEquals(r, this.defaultServicesManagerImpl.findServiceBy(r.getId())); + + this.defaultServicesManagerImpl.delete(r.getId()); + assertNull(this.defaultServicesManagerImpl.findServiceBy(r.getId())); + } + + @Test + public void verifyDeleteNotExistentService() { + assertNull(this.defaultServicesManagerImpl.delete(1500)); + } + + @Test + public void verifyMatchesExistingService() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setId(1000); + r.setName("test"); + r.setServiceId("test"); + + final Service service = new SimpleService("test"); + final Service service2 = new SimpleService("fdfa"); + + this.defaultServicesManagerImpl.save(r); + + assertTrue(this.defaultServicesManagerImpl.matchesExistingService(service)); + assertEquals(r, this.defaultServicesManagerImpl.findServiceBy(service)); + assertNull(this.defaultServicesManagerImpl.findServiceBy(service2)); + } + + @Test + public void verifyAllService() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setId(1000); + r.setName("test"); + r.setServiceId("test"); + r.setEvaluationOrder(2); + + this.defaultServicesManagerImpl.save(r); + + assertEquals(2, this.defaultServicesManagerImpl.getAllServices().size()); + assertTrue(this.defaultServicesManagerImpl.getAllServices().contains(r)); + } + + @Test + public void verifyRegexService() { + final RegexRegisteredService r = new RegexRegisteredService(); + r.setId(10000); + r.setName("regex test"); + r.setServiceId("^http://www.test.edu.+"); + r.setEvaluationOrder(10000); + + this.defaultServicesManagerImpl.save(r); + + final SimpleService service = new SimpleService("HTTP://www.TEST.edu/param=hello"); + assertEquals(r, this.defaultServicesManagerImpl.findServiceBy(service)); + } + + @Test + public void verifyEmptyServicesRegistry() { + final SimpleService s = new SimpleService("http://www.google.com"); + + for (final RegisteredService svc : defaultServicesManagerImpl.getAllServices()) { + defaultServicesManagerImpl.delete(svc.getId()); + } + assertTrue(this.defaultServicesManagerImpl.getAllServices().size() == 0); + assertNull(this.defaultServicesManagerImpl.findServiceBy(s)); + assertNull(this.defaultServicesManagerImpl.findServiceBy(1000)); + } + + @Test + public void verifyEvaluationOrderOfServices() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setId(100); + r.setName("test"); + r.setServiceId("test"); + r.setEvaluationOrder(200); + + final RegisteredServiceImpl r2 = new RegisteredServiceImpl(); + r2.setId(101); + r2.setName("test"); + r2.setServiceId("test"); + r2.setEvaluationOrder(80); + + final RegisteredServiceImpl r3 = new RegisteredServiceImpl(); + r3.setId(102); + r3.setName("Sample test service"); + r3.setServiceId("test"); + r3.setEvaluationOrder(80); + + this.defaultServicesManagerImpl.save(r); + this.defaultServicesManagerImpl.save(r3); + this.defaultServicesManagerImpl.save(r2); + + final List allServices = new ArrayList<>( + this.defaultServicesManagerImpl.getAllServices()); + + //We expect the 3 newly added services, plus the one added in setUp() + assertEquals(4, allServices.size()); + + assertEquals(allServices.get(0).getId(), r3.getId()); + assertEquals(allServices.get(1).getId(), r2.getId()); + assertEquals(allServices.get(2).getId(), r.getId()); + + } + + private static class SimpleService implements Service { + + /** + * Comment for serialVersionUID. + */ + private static final long serialVersionUID = 6572142033945243669L; + + private final String id; + + protected SimpleService(final String id) { + this.id = id; + } + + public Map getAttributes() { + return null; + } + + public String getId() { + return this.id; + } + + public void setPrincipal(final Principal principal) { + // nothing to do + } + + public boolean matches(final Service service) { + return true; + } + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/InMemoryServiceRegistryDaoImplTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/InMemoryServiceRegistryDaoImplTests.java new file mode 100644 index 000000000000..dd7bc0c5fc52 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/InMemoryServiceRegistryDaoImplTests.java @@ -0,0 +1,71 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.services; + +import org.jasig.cas.TestUtils; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * This is test cases for {@link org.jasig.cas.services.InMemoryServiceRegistryDaoImpl}. + * @author Misagh Moayyed mmoayyed@unicon.net + * @since 4.1.0 + */ +public class InMemoryServiceRegistryDaoImplTests { + + @Test + public void verifySave() { + final InMemoryServiceRegistryDaoImpl reg = new InMemoryServiceRegistryDaoImpl(); + final RegisteredService svc = TestUtils.getRegisteredService("service"); + assertEquals(reg.save(svc), svc); + } + + @Test + public void verifyLoadEmpty() { + final InMemoryServiceRegistryDaoImpl reg = new InMemoryServiceRegistryDaoImpl(); + assertEquals(reg.load().size(), 0); + } + + @Test + public void verifySaveAndLoad() { + final InMemoryServiceRegistryDaoImpl reg = new InMemoryServiceRegistryDaoImpl(); + final RegisteredService svc = TestUtils.getRegisteredService("service"); + assertEquals(reg.save(svc), svc); + assertEquals(reg.load().size(), 1); + } + + @Test + public void verifySaveAndFind() { + final InMemoryServiceRegistryDaoImpl reg = new InMemoryServiceRegistryDaoImpl(); + final RegisteredService svc = TestUtils.getRegisteredService("service"); + assertEquals(reg.save(svc), svc); + assertEquals(reg.findServiceById(svc.getId()), svc); + } + + @Test + public void verifySaveAndDelete() { + final InMemoryServiceRegistryDaoImpl reg = new InMemoryServiceRegistryDaoImpl(); + final RegisteredService svc = TestUtils.getRegisteredService("service"); + assertEquals(reg.save(svc), svc); + assertTrue(reg.delete(svc)); + assertEquals(reg.load().size(), 0); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/JsonServiceRegistryDaoTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/JsonServiceRegistryDaoTests.java new file mode 100644 index 000000000000..34d07e4bf182 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/JsonServiceRegistryDaoTests.java @@ -0,0 +1,335 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import com.google.common.collect.Sets; +import org.apache.commons.io.FileUtils; +import org.jasig.cas.authentication.principal.CachingPrincipalAttributesRepository; +import org.jasig.cas.authentication.principal.ShibbolethCompatiblePersistentIdGenerator; +import org.jasig.cas.services.support.RegisteredServiceRegexAttributeFilter; +import org.jasig.services.persondir.support.StubPersonAttributeDao; +import org.jasig.services.persondir.support.merger.ReplacingAttributeAdder; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +/** + * Handles test cases for {@link JsonServiceRegistryDao}. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class JsonServiceRegistryDaoTests { + + private static final ClassPathResource RESOURCE = new ClassPathResource("services"); + + private final ServiceRegistryDao dao; + + public JsonServiceRegistryDaoTests() throws Exception { + this.dao = new JsonServiceRegistryDao(RESOURCE.getFile()); + } + + @BeforeClass + public static void prepTests() throws Exception { + FileUtils.cleanDirectory(RESOURCE.getFile()); + } + + @Test + public void checkSaveMethodWithNonExistentServiceAndNoAttributes() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setName("testSaveMethodWithNonExistentServiceAndNoAttributes"); + r.setServiceId("testId"); + r.setTheme("theme"); + r.setDescription("description"); + + final RegisteredService r2 = this.dao.save(r); + final RegisteredService r3 = this.dao.findServiceById(r2.getId()); + + assertEquals(r, r2); + assertEquals(r2, r3); + } + + @Test + public void execSaveMethodWithDefaultUsernameAttribute() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setName("testSaveMethodWithDefaultUsernameAttribute"); + r.setServiceId("testId"); + r.setTheme("theme"); + r.setDescription("description"); + r.setUsernameAttributeProvider(new DefaultRegisteredServiceUsernameProvider()); + final RegisteredService r2 = this.dao.save(r); + assertEquals(r2, r); + } + + @Test + public void ensureSaveMethodWithDefaultPrincipalAttribute() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setName("testSaveMethodWithDefaultPrincipalAttribute"); + r.setServiceId("testId"); + r.setTheme("theme"); + r.setDescription("description"); + r.setUsernameAttributeProvider(new PrincipalAttributeRegisteredServiceUsernameProvider("cn")); + final RegisteredService r2 = this.dao.save(r); + assertEquals(r2, r); + } + @Test + public void verifySaveMethodWithDefaultAnonymousAttribute() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setName("testSaveMethodWithDefaultAnonymousAttribute"); + r.setServiceId("testId"); + r.setTheme("theme"); + r.setDescription("description"); + r.setUsernameAttributeProvider(new AnonymousRegisteredServiceUsernameAttributeProvider( + new ShibbolethCompatiblePersistentIdGenerator("helloworld") + )); + final RegisteredService r2 = this.dao.save(r); + final RegisteredService r3 = this.dao.findServiceById(r2.getId()); + assertEquals(r2, r3); + } + + @Test + public void verifySaveAttributeReleasePolicy() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setName("testSaveAttributeReleasePolicy"); + r.setServiceId("testId"); + r.setTheme("theme"); + r.setDescription("description"); + r.setAttributeReleasePolicy(new ReturnAllAttributeReleasePolicy()); + + final RegisteredService r2 = this.dao.save(r); + final RegisteredService r3 = this.dao.findServiceById(r2.getId()); + + assertEquals(r, r2); + assertEquals(r2, r3); + assertNotNull(r3.getAttributeReleasePolicy()); + assertEquals(r2.getAttributeReleasePolicy(), r3.getAttributeReleasePolicy()); + } + + @Test + public void verifySaveMethodWithExistingServiceNoAttribute() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setName("testSaveMethodWithExistingServiceNoAttribute"); + r.setServiceId("testId"); + r.setTheme("theme"); + r.setDescription("description"); + this.dao.save(r); + r.setTheme("mytheme"); + + this.dao.save(r); + + final RegisteredService r3 = this.dao.findServiceById(r.getId()); + assertEquals(r, r3); + } + + @Test + public void verifySaveAttributeReleasePolicyMappingRules() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setName("testSaveAttributeReleasePolicyMappingRules"); + r.setServiceId("testId"); + + final Map map = new HashMap<>(); + map.put("attr1", "newattr1"); + map.put("attr2", "newattr2"); + map.put("attr2", "newattr3"); + + + final ReturnMappedAttributeReleasePolicy policy = new ReturnMappedAttributeReleasePolicy(); + policy.setAllowedAttributes(map); + r.setAttributeReleasePolicy(policy); + + final RegisteredService r2 = this.dao.save(r); + final RegisteredService r3 = this.dao.findServiceById(r2.getId()); + + assertEquals(r, r2); + assertEquals(r2, r3); + assertNotNull(r3.getAttributeReleasePolicy()); + assertEquals(r2.getAttributeReleasePolicy(), r3.getAttributeReleasePolicy()); + } + + @Test + public void verifySaveAttributeReleasePolicyAllowedAttrRules() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setName("testSaveAttributeReleasePolicyAllowedAttrRules"); + r.setServiceId("testId"); + + final ReturnAllowedAttributeReleasePolicy policy = new ReturnAllowedAttributeReleasePolicy(); + policy.setAllowedAttributes(Arrays.asList("1", "2", "3")); + r.setAttributeReleasePolicy(policy); + + final RegisteredService r2 = this.dao.save(r); + final RegisteredService r3 = this.dao.findServiceById(r2.getId()); + + assertEquals(r, r2); + assertEquals(r2, r3); + assertNotNull(r3.getAttributeReleasePolicy()); + assertEquals(r2.getAttributeReleasePolicy(), r3.getAttributeReleasePolicy()); + } + + @Test + public void verifySaveAttributeReleasePolicyAllowedAttrRulesAndFilter() { + final RegexRegisteredService r = new RegexRegisteredService(); + r.setName("testSaveAttributeReleasePolicyAllowedAttrRulesAndFilter"); + r.setServiceId("testId"); + r.setTheme("testtheme"); + r.setEvaluationOrder(1000); + r.setAccessStrategy(new DefaultRegisteredServiceAccessStrategy(true, false)); + r.setProxyPolicy(new RegexMatchingRegisteredServiceProxyPolicy("https://.+")); + r.setRequiredHandlers(new HashSet(Arrays.asList("h1", "h2"))); + + final ReturnAllowedAttributeReleasePolicy policy = new ReturnAllowedAttributeReleasePolicy(); + policy.setAllowedAttributes(Arrays.asList("1", "2", "3")); + r.setAttributeReleasePolicy(policy); + r.getAttributeReleasePolicy().setAttributeFilter(new RegisteredServiceRegexAttributeFilter("\\w+")); + + final RegisteredService r2 = this.dao.save(r); + final RegisteredService r3 = this.dao.findServiceById(r2.getId()); + + assertEquals(r, r2); + assertEquals(r2, r3); + assertNotNull(r3.getAttributeReleasePolicy()); + assertEquals(r2.getAttributeReleasePolicy(), r3.getAttributeReleasePolicy()); + } + + @Test + public void verifyServiceType() { + final RegexRegisteredService r = new RegexRegisteredService(); + r.setServiceId("^https://.+"); + r.setName("testServiceType"); + r.setTheme("testtheme"); + r.setEvaluationOrder(1000); + + final RegisteredService r2 = this.dao.save(r); + assertTrue(r2 instanceof RegexRegisteredService); + } + + @Test(expected=RuntimeException.class) + public void verifyServiceWithInvalidFileName() { + final RegexRegisteredService r = new RegexRegisteredService(); + r.setServiceId("^https://.+"); + r.setName("hell/o@world:*"); + r.setEvaluationOrder(1000); + + final RegisteredService r2 = this.dao.save(r); + } + + @Test + public void checkLoadingOfJsonServiceFiles() throws Exception { + prepTests(); + verifySaveAttributeReleasePolicyAllowedAttrRulesWithCaching(); + verifySaveAttributeReleasePolicyAllowedAttrRulesAndFilter(); + assertEquals(this.dao.load().size(), 2); + } + + @Test + public void verifySaveAttributeReleasePolicyAllowedAttrRulesWithCaching() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setName("testSaveAttributeReleasePolicyAllowedAttrRulesWithCaching"); + r.setServiceId("testId"); + + final ReturnAllowedAttributeReleasePolicy policy = new ReturnAllowedAttributeReleasePolicy(); + policy.setAllowedAttributes(Arrays.asList("1", "2", "3")); + + final Map> attributes = new HashMap<>(); + attributes.put("values", Arrays.asList(new Object[]{"v1", "v2", "v3"})); + + final CachingPrincipalAttributesRepository repository = + new CachingPrincipalAttributesRepository( + new StubPersonAttributeDao(attributes), + TimeUnit.MILLISECONDS, 100); + repository.setMergingStrategy(new ReplacingAttributeAdder()); + + policy.setPrincipalAttributesRepository(repository); + r.setAttributeReleasePolicy(policy); + + final RegisteredService r2 = this.dao.save(r); + final RegisteredService r3 = this.dao.findServiceById(r2.getId()); + + assertEquals(r, r2); + assertEquals(r2, r3); + assertNotNull(r3.getAttributeReleasePolicy()); + assertEquals(r2.getAttributeReleasePolicy(), r3.getAttributeReleasePolicy()); + } + + @Test + public void verifyServiceRemovals() { + final List list = new ArrayList<>(5); + for (int i = 1; i < 5; i++) { + final RegexRegisteredService r = new RegexRegisteredService(); + r.setServiceId("^https://.+"); + r.setName("testServiceType"); + r.setTheme("testtheme"); + r.setEvaluationOrder(1000); + r.setId(i * 100); + list.add(this.dao.save(r)); + } + + for (final RegisteredService r2 : list) { + this.dao.delete(r2); + assertNull(this.dao.findServiceById(r2.getId())); + } + + } + + @Test + public void checkForAuthorizationStrategy() { + final RegexRegisteredService r = new RegexRegisteredService(); + r.setServiceId("^https://.+"); + r.setName("checkForAuthorizationStrategy"); + r.setId(42); + + final DefaultRegisteredServiceAccessStrategy authz = + new DefaultRegisteredServiceAccessStrategy(false, false); + + final Map> attrs = new HashMap<>(); + attrs.put("cn", Sets.newHashSet("v1, v2, v3")); + attrs.put("memberOf", Sets.newHashSet(Arrays.asList("v4, v5, v6"))); + authz.setRequiredAttributes(attrs); + r.setAccessStrategy(authz); + + this.dao.save(r); + final List list = this.dao.load(); + assertEquals(list.size(), 1); + } + + @Test + public void serializePublicKeyForServiceAndVerify() throws Exception { + final RegisteredServicePublicKey publicKey = new RegisteredServicePublicKeyImpl( + "classpath:RSA1024Public.key", "RSA"); + + final RegexRegisteredService r = new RegexRegisteredService(); + r.setServiceId("^https://.+"); + r.setName("serializePublicKeyForServiceAndVerify"); + r.setId(4245); + r.setPublicKey(publicKey); + + this.dao.save(r); + final List list = this.dao.load(); + assertNotNull(this.dao.findServiceById(r.getId())); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/PrincipalAttributeRegisteredServiceUsernameProviderTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/PrincipalAttributeRegisteredServiceUsernameProviderTests.java new file mode 100644 index 000000000000..fe31ecf0d9eb --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/PrincipalAttributeRegisteredServiceUsernameProviderTests.java @@ -0,0 +1,82 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.principal.Principal; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class PrincipalAttributeRegisteredServiceUsernameProviderTests { + @Test + public void verifyUsernameByPrincipalAttribute() { + final PrincipalAttributeRegisteredServiceUsernameProvider provider = + new PrincipalAttributeRegisteredServiceUsernameProvider("cn"); + + final Map attrs = new HashMap<>(); + attrs.put("userid", "u1"); + attrs.put("cn", "TheName"); + + final Principal p = mock(Principal.class); + when(p.getId()).thenReturn("person"); + when(p.getAttributes()).thenReturn(attrs); + + final String id = provider.resolveUsername(p, TestUtils.getService()); + assertEquals(id, "TheName"); + + } + + @Test + public void verifyUsernameByPrincipalAttributeNotFound() { + final PrincipalAttributeRegisteredServiceUsernameProvider provider = + new PrincipalAttributeRegisteredServiceUsernameProvider("cn"); + + final Map attrs = new HashMap<>(); + attrs.put("userid", "u1"); + + final Principal p = mock(Principal.class); + when(p.getId()).thenReturn("person"); + when(p.getAttributes()).thenReturn(attrs); + + final String id = provider.resolveUsername(p, TestUtils.getService()); + assertEquals(id, p.getId()); + + } + + @Test + public void verifyEquality() { + final PrincipalAttributeRegisteredServiceUsernameProvider provider = + new PrincipalAttributeRegisteredServiceUsernameProvider("cn"); + + final PrincipalAttributeRegisteredServiceUsernameProvider provider2 = + new PrincipalAttributeRegisteredServiceUsernameProvider("cn"); + + assertEquals(provider, provider2); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/RegexRegisteredServiceTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/RegexRegisteredServiceTests.java new file mode 100644 index 000000000000..3145471fd134 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/RegexRegisteredServiceTests.java @@ -0,0 +1,130 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.mock.MockService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; + +/** + * Unit test for {@link RegexRegisteredService}. + * + * @author Marvin S. Addison + * @since 3.4.0 + */ +@RunWith(Parameterized.class) +public class RegexRegisteredServiceTests { + + private final RegexRegisteredService service; + + private final String serviceToMatch; + + private final boolean expected; + + public RegexRegisteredServiceTests( + final RegexRegisteredService service, + final String serviceToMatch, + final boolean expectedResult) { + this.service = service; + this.serviceToMatch = serviceToMatch; + this.expected = expectedResult; + } + + @Parameterized.Parameters + public static Collection getParameters() { + final String domainCatchallHttp = "https*://([A-Za-z0-9_-]+\\.)+vt\\.edu/.*"; + final String domainCatchallHttpImap = "(https*|imaps*)://([A-Za-z0-9_-]+\\.)+vt\\.edu/.*"; + final String globalCatchallHttpImap = "(https*|imaps*)://.*"; + return Arrays.asList(new Object[][]{ + // CAS-1071 domain-specific HTTP catch-all #1 + { + newService(domainCatchallHttp), + "https://service.vt.edu/webapp?a=1", + true, + }, + // CAS-1071 domain-specific HTTP catch-all #2 + { + newService(domainCatchallHttp), + "http://test-01.service.vt.edu/webapp?a=1", + true, + }, + // CAS-1071 domain-specific HTTP catch-all #3 + { + newService(domainCatchallHttp), + "https://thepiratebay.se?service.vt.edu/webapp?a=1", + false, + }, + // Domain-specific catch-all for HTTP(S)/IMAP(S) #1 + { + newService(domainCatchallHttpImap), + "http://test_service.vt.edu/login", + true, + }, + // Domain-specific catch-all for HTTP(S)/IMAP(S) #2 + { + newService(domainCatchallHttpImap), + "imaps://imap-server-01.vt.edu/", + true, + }, + // Global catch-all for HTTP(S)/IMAP(S) #1 + { + newService(globalCatchallHttpImap), + "https://host-01.example.com/", + true, + }, + // Global catch-all for HTTP(S)/IMAP(S) #2 + { + newService(globalCatchallHttpImap), + "imap://host-02.example.edu/", + true, + }, + // Null case + { + newService(globalCatchallHttpImap), + null, + false, + }, + }); + } + + @Test + public void verifyMatches() throws Exception { + final Service testService; + if (serviceToMatch == null) { + testService = null; + } else { + testService = new MockService(serviceToMatch); + } + assertEquals(expected, service.matches(testService)); + } + + + private static RegexRegisteredService newService(final String id) { + final RegexRegisteredService service = new RegexRegisteredService(); + service.setServiceId(id); + return service; + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/RegisteredServiceImplTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/RegisteredServiceImplTests.java new file mode 100644 index 000000000000..17c3c2983773 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/RegisteredServiceImplTests.java @@ -0,0 +1,99 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.mock.MockService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; + +/** + * Unit test for {@link RegisteredServiceImpl}. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.1 + * + */ +@RunWith(Parameterized.class) +public class RegisteredServiceImplTests { + + private final RegisteredServiceImpl service; + + private final String serviceToMatch; + + private final boolean expected; + + public RegisteredServiceImplTests( + final RegisteredServiceImpl service, + final String serviceToMatch, + final boolean expectedResult) { + this.service = service; + this.serviceToMatch = serviceToMatch; + this.expected = expectedResult; + } + + @Parameterized.Parameters + public static Collection getParameters() { + return Arrays.asList(new Object[][]{ + // Allow all paths on single host + { + newService("https://host.vt.edu/**"), + "https://host.vt.edu/a/b/c?a=1&b=2", + true, + }, + // Global catch-all for HTTP + { + newService("http://**"), + "http://host.subdomain.example.com/service", + true, + }, + // Null case + { + newService("https:/example.com/**"), + null, + false, + }, + }); + } + + @Test + public void verifyMatches() throws Exception { + final Service testService; + if (serviceToMatch == null) { + testService = null; + } else { + testService = new MockService(serviceToMatch); + } + assertEquals(expected, service.matches(testService)); + } + + + private static RegisteredServiceImpl newService(final String id) { + final RegisteredServiceImpl service = new RegisteredServiceImpl(); + service.setServiceId(id); + return service; + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/UnauthorizedProxyingExceptionTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/UnauthorizedProxyingExceptionTests.java new file mode 100644 index 000000000000..832c3f1646d5 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/UnauthorizedProxyingExceptionTests.java @@ -0,0 +1,54 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author Misagh Moayyed + * @since 4.0.0 + */ +public class UnauthorizedProxyingExceptionTests { + + private static final String MESSAGE = "GG"; + + @Test + public void verifyGetCode() { + final UnauthorizedProxyingException e = new UnauthorizedProxyingException(); + assertEquals(UnauthorizedProxyingException.CODE, e.getMessage()); + } + + @Test + public void verifyCodeConstructor() { + final UnauthorizedProxyingException e = new UnauthorizedProxyingException(MESSAGE); + + assertEquals(MESSAGE, e.getMessage()); + } + + @Test + public void verifyThrowableConstructorWithCode() { + final RuntimeException r = new RuntimeException(); + final UnauthorizedProxyingException e = new UnauthorizedProxyingException(MESSAGE, r); + + assertEquals(MESSAGE, e.getMessage()); + assertEquals(r, e.getCause()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/UnauthorizedServiceExceptionTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/UnauthorizedServiceExceptionTests.java new file mode 100644 index 000000000000..2ae0cfd90124 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/UnauthorizedServiceExceptionTests.java @@ -0,0 +1,48 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * @author Misagh Moayyed + * @since 4.0.0 + */ +public class UnauthorizedServiceExceptionTests { + + private static final String MESSAGE = "GG"; + + @Test + public void verifyCodeConstructor() { + final UnauthorizedServiceException e = new UnauthorizedServiceException(MESSAGE); + + assertEquals(MESSAGE, e.getMessage()); + } + + @Test + public void verifyThrowableConstructorWithCode() { + final RuntimeException r = new RuntimeException(); + final UnauthorizedServiceException e = new UnauthorizedServiceException(MESSAGE, r); + + assertEquals(MESSAGE, e.getMessage()); + assertEquals(r, e.getCause()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/UnauthorizedSsoServiceExceptionTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/UnauthorizedSsoServiceExceptionTests.java new file mode 100644 index 000000000000..3584ca4cf643 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/UnauthorizedSsoServiceExceptionTests.java @@ -0,0 +1,55 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * @author Misagh Moayyed + * @since 4.0.0 + */ +public class UnauthorizedSsoServiceExceptionTests { + + private static final String CODE = "service.not.authorized.sso"; + private static final String MESSAGE = "GG"; + + @Test + public void verifyGetCode() { + final UnauthorizedSsoServiceException e = new UnauthorizedSsoServiceException(); + assertEquals(CODE, e.getMessage()); + } + + @Test + public void verifyCodeConstructor() { + final UnauthorizedSsoServiceException e = new UnauthorizedSsoServiceException(MESSAGE); + + assertEquals(MESSAGE, e.getMessage()); + } + + @Test + public void verifyThrowableConstructorWithCode() { + final RuntimeException r = new RuntimeException(); + final UnauthorizedSsoServiceException e = new UnauthorizedSsoServiceException(MESSAGE, r); + + assertEquals(MESSAGE, e.getMessage()); + assertEquals(r, e.getCause()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/support/RegisteredServiceRegexAttributeFilterTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/support/RegisteredServiceRegexAttributeFilterTests.java new file mode 100644 index 000000000000..6fcbd6d36ef9 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/support/RegisteredServiceRegexAttributeFilterTests.java @@ -0,0 +1,108 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services.support; + +import org.apache.commons.lang3.SerializationUtils; +import org.jasig.cas.services.AttributeFilter; +import org.jasig.cas.services.RegisteredService; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.when; + +/** + * @author Misagh Moayyed + * @since 4.0.0 + */ +public class RegisteredServiceRegexAttributeFilterTests { + + private final AttributeFilter filter; + private final Map givenAttributesMap; + + @Mock + private RegisteredService registeredService; + + public RegisteredServiceRegexAttributeFilterTests() { + + this.filter = new RegisteredServiceRegexAttributeFilter("^.{5,}$"); + + this.givenAttributesMap = new HashMap<>(); + this.givenAttributesMap.put("uid", "loggedInTestUid"); + this.givenAttributesMap.put("phone", "1290"); + this.givenAttributesMap.put("familyName", "Smith"); + this.givenAttributesMap.put("givenName", "John"); + this.givenAttributesMap.put("employeeId", "E1234"); + this.givenAttributesMap.put("memberOf", Arrays.asList("math", "science", "chemistry")); + this.givenAttributesMap.put("arrayAttribute", new String[] {"math", "science", "chemistry"}); + this.givenAttributesMap.put("setAttribute", new HashSet(Arrays.asList("math", "science", "chemistry"))); + + final Map mapAttributes = new HashMap<>(); + mapAttributes.put("uid", "loggedInTestUid"); + mapAttributes.put("phone", "890"); + mapAttributes.put("familyName", "Smith"); + this.givenAttributesMap.put("mapAttribute", mapAttributes); + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(this.registeredService.getName()).thenReturn("sample test service"); + when(this.registeredService.getServiceId()).thenReturn("https://www.jasig.org"); + } + + @Test + public void verifyPatternFilter() { + + final Map attrs = this.filter.filter(this.givenAttributesMap); + assertEquals(attrs.size(), 7); + + assertFalse(attrs.containsKey("phone")); + assertFalse(attrs.containsKey("givenName")); + + assertTrue(attrs.containsKey("uid")); + assertTrue(attrs.containsKey("memberOf")); + assertTrue(attrs.containsKey("mapAttribute")); + + @SuppressWarnings("unchecked") + final Map mapAttributes = (Map) attrs.get("mapAttribute"); + assertTrue(mapAttributes.containsKey("uid")); + assertTrue(mapAttributes.containsKey("familyName")); + assertFalse(mapAttributes.containsKey("phone")); + + final List obj = (List) attrs.get("memberOf"); + assertEquals(2, obj.size()); + } + + @Test + public void verifySerialization() { + final byte[] data = SerializationUtils.serialize(this.filter); + final AttributeFilter secondFilter = SerializationUtils.deserialize(data); + assertEquals(secondFilter, this.filter); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/InvalidTicketExceptionTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/InvalidTicketExceptionTests.java new file mode 100644 index 000000000000..6eed0dd1a8ad --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/InvalidTicketExceptionTests.java @@ -0,0 +1,46 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket; + +import static org.junit.Assert.*; + +import org.jasig.cas.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.handler.BadCredentialsAuthenticationException; +import org.junit.Test; + +/** + * @author Misagh Moayyed + * @since 3.0.0 + */ +public class InvalidTicketExceptionTests { + + @Test + public void verifyCodeNoThrowable() { + final TicketException t = new InvalidTicketException("InvalidTicketId"); + assertEquals("INVALID_TICKET", t.getCode()); + } + + @Test + public void verifyCodeWithThrowable() { + final AuthenticationException a = new BadCredentialsAuthenticationException(); + final TicketException t = new InvalidTicketException(a, "InvalidTicketId"); + + assertEquals(a.toString(), t.getCode()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/ServiceTicketImplTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/ServiceTicketImplTests.java new file mode 100644 index 000000000000..e0c01c39ea6c --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/ServiceTicketImplTests.java @@ -0,0 +1,135 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket; + +import static org.junit.Assert.*; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.ticket.support.MultiTimeUseOrTimeoutExpirationPolicy; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.junit.Test; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class ServiceTicketImplTests { + + private final TicketGrantingTicketImpl ticketGrantingTicket = new TicketGrantingTicketImpl("test", + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + + private final UniqueTicketIdGenerator uniqueTicketIdGenerator = new DefaultUniqueTicketIdGenerator(); + + @Test(expected = Exception.class) + public void verifyNoService() { + new ServiceTicketImpl("stest1", this.ticketGrantingTicket, null, true, new NeverExpiresExpirationPolicy()); + } + + @Test(expected = Exception.class) + public void verifyNoTicket() { + new ServiceTicketImpl("stest1", null, TestUtils.getService(), true, new NeverExpiresExpirationPolicy()); + } + + @Test + public void verifyIsFromNewLoginTrue() { + final ServiceTicket s = new ServiceTicketImpl("stest1", this.ticketGrantingTicket, TestUtils.getService(), true, + new NeverExpiresExpirationPolicy()); + assertTrue(s.isFromNewLogin()); + } + + @Test + public void verifyIsFromNewLoginFalse() { + final ServiceTicket s = new ServiceTicketImpl("stest1", this.ticketGrantingTicket, TestUtils.getService(), false, + new NeverExpiresExpirationPolicy()); + assertFalse(s.isFromNewLogin()); + } + + @Test + public void verifyGetService() { + final Service simpleService = TestUtils.getService(); + final ServiceTicket s = new ServiceTicketImpl("stest1", this.ticketGrantingTicket, simpleService, false, + new NeverExpiresExpirationPolicy()); + assertEquals(simpleService, s.getService()); + } + + @Test + public void verifyGetTicket() { + final Service simpleService = TestUtils.getService(); + final ServiceTicket s = new ServiceTicketImpl("stest1", this.ticketGrantingTicket, simpleService, false, + new NeverExpiresExpirationPolicy()); + assertEquals(this.ticketGrantingTicket, s.getGrantingTicket()); + } + + @Test + public void verifyIsExpiredTrueBecauseOfRoot() { + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy()); + final ServiceTicket s = t.grantServiceTicket(this.uniqueTicketIdGenerator.getNewTicketId(ServiceTicket.PREFIX), + TestUtils.getService(), new NeverExpiresExpirationPolicy(), false); + t.markTicketExpired(); + + assertTrue(s.isExpired()); + } + + @Test + public void verifyIsExpiredFalse() { + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy()); + final ServiceTicket s = t.grantServiceTicket(this.uniqueTicketIdGenerator.getNewTicketId(ServiceTicket.PREFIX), + TestUtils.getService(), new MultiTimeUseOrTimeoutExpirationPolicy(1, 5000), false); + assertFalse(s.isExpired()); + } + + @Test + public void verifyTicketGrantingTicket() { + final Authentication a = TestUtils.getAuthentication(); + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy()); + final ServiceTicket s = t.grantServiceTicket(this.uniqueTicketIdGenerator.getNewTicketId(ServiceTicket.PREFIX), + TestUtils.getService(), new MultiTimeUseOrTimeoutExpirationPolicy(1, 5000), false); + final TicketGrantingTicket t1 = s.grantTicketGrantingTicket( + this.uniqueTicketIdGenerator.getNewTicketId(TicketGrantingTicket.PREFIX), a, + new NeverExpiresExpirationPolicy()); + + assertEquals(a, t1.getAuthentication()); + } + + @Test + public void verifyTicketGrantingTicketGrantedTwice() { + final Authentication a = TestUtils.getAuthentication(); + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy()); + final ServiceTicket s = t.grantServiceTicket(this.uniqueTicketIdGenerator.getNewTicketId(ServiceTicket.PREFIX), + TestUtils.getService(), new MultiTimeUseOrTimeoutExpirationPolicy(1, 5000), false); + s.grantTicketGrantingTicket(this.uniqueTicketIdGenerator.getNewTicketId(TicketGrantingTicket.PREFIX), a, + new NeverExpiresExpirationPolicy()); + + try { + s.grantTicketGrantingTicket(this.uniqueTicketIdGenerator.getNewTicketId(TicketGrantingTicket.PREFIX), a, + new NeverExpiresExpirationPolicy()); + fail("Exception expected."); + } catch (final Exception e) { + return; + } + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/TicketCreationExceptionTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/TicketCreationExceptionTests.java new file mode 100644 index 000000000000..e3ce2d5558ec --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/TicketCreationExceptionTests.java @@ -0,0 +1,44 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author Scott Battaglia + + * @since 3.0.0 + */ +public class TicketCreationExceptionTests { + + @Test + public void verifyNoParamConstructor() { + new TicketCreationException(); + } + + @Test + public void verifyThrowableParamConstructor() { + final Throwable throwable = new Throwable(); + final TicketCreationException t = new TicketCreationException(throwable); + + assertEquals(throwable, t.getCause()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/TicketGrantingTicketImplTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/TicketGrantingTicketImplTests.java new file mode 100644 index 000000000000..95983621d9e0 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/TicketGrantingTicketImplTests.java @@ -0,0 +1,192 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.mock.MockService; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class TicketGrantingTicketImplTests { + + private final UniqueTicketIdGenerator uniqueTicketIdGenerator = new DefaultUniqueTicketIdGenerator(); + + @Test + public void verifyEquals() { + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", null, null, + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + + assertNotNull(t); + assertFalse(t.equals(new Object())); + assertTrue(t.equals(t)); + } + + @Test(expected=Exception.class) + public void verifyNullAuthentication() { + new TicketGrantingTicketImpl("test", null, null, null, + new NeverExpiresExpirationPolicy()); + } + + @Test + public void verifyGetAuthentication() { + final Authentication authentication = TestUtils.getAuthentication(); + + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", null, null, + authentication, new NeverExpiresExpirationPolicy()); + + assertEquals(t.getAuthentication(), authentication); + assertEquals(t.getId(), t.toString()); + } + + @Test + public void verifyIsRootTrue() { + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", null, null, + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + + assertTrue(t.isRoot()); + } + + @Test + public void verifyIsRootFalse() { + final TicketGrantingTicketImpl t1 = new TicketGrantingTicketImpl("test", null, null, + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", + new SimpleWebApplicationServiceImpl("gantor"), t1, + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + + assertFalse(t.isRoot()); + } + + @Test + public void verifyGetChainedPrincipalsWithOne() { + final Authentication authentication = TestUtils.getAuthentication(); + final List principals = new ArrayList<>(); + principals.add(authentication); + + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", null, null, + authentication, new NeverExpiresExpirationPolicy()); + + assertEquals(principals, t.getChainedAuthentications()); + } + + @Test + public void verifyCheckCreationTime() { + final Authentication authentication = TestUtils.getAuthentication(); + final List principals = new ArrayList<>(); + principals.add(authentication); + + final long startTime = System.currentTimeMillis(); + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", null, null, + authentication, new NeverExpiresExpirationPolicy()); + final long finishTime = System.currentTimeMillis(); + assertTrue(startTime <= t.getCreationTime() && finishTime >= t.getCreationTime()); + } + + @Test + public void verifyGetChainedPrincipalsWithTwo() { + final Authentication authentication = TestUtils.getAuthentication(); + final Authentication authentication1 = TestUtils.getAuthentication("test1"); + final List principals = new ArrayList<>(); + principals.add(authentication); + principals.add(authentication1); + + final TicketGrantingTicketImpl t1 = new TicketGrantingTicketImpl("test", null, null, + authentication1, new NeverExpiresExpirationPolicy()); + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", + new SimpleWebApplicationServiceImpl("gantor"), t1, + authentication, new NeverExpiresExpirationPolicy()); + + assertEquals(principals, t.getChainedAuthentications()); + } + + @Test + public void verifyServiceTicketAsFromInitialCredentials() { + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", null, null, + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + final ServiceTicket s = t.grantServiceTicket(this.uniqueTicketIdGenerator + .getNewTicketId(ServiceTicket.PREFIX), TestUtils.getService(), + new NeverExpiresExpirationPolicy(), false); + + assertTrue(s.isFromNewLogin()); + } + + @Test + public void verifyServiceTicketAsFromNotInitialCredentials() { + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", null, null, + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + + t.grantServiceTicket( + this.uniqueTicketIdGenerator.getNewTicketId(ServiceTicket.PREFIX), + TestUtils.getService(), + new NeverExpiresExpirationPolicy(), + false); + final ServiceTicket s = t.grantServiceTicket( + this.uniqueTicketIdGenerator.getNewTicketId(ServiceTicket.PREFIX), + TestUtils.getService(), + new NeverExpiresExpirationPolicy(), + false); + + assertFalse(s.isFromNewLogin()); + } + + @Test + public void verifyWebApplicationServices() { + final MockService testService = new MockService("test"); + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", null, null, + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + t.grantServiceTicket(this.uniqueTicketIdGenerator + .getNewTicketId(ServiceTicket.PREFIX), testService, + new NeverExpiresExpirationPolicy(), false); + Map services = t.getServices(); + assertEquals(1, services.size()); + final String ticketId = services.keySet().iterator().next(); + assertEquals(testService, services.get(ticketId)); + t.removeAllServices(); + services = t.getServices(); + assertEquals(0, services.size()); + } + + @Test + public void verifyWebApplicationExpire() { + final MockService testService = new MockService("test"); + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", null, null, + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + t.grantServiceTicket(this.uniqueTicketIdGenerator + .getNewTicketId(ServiceTicket.PREFIX), testService, + new NeverExpiresExpirationPolicy(), false); + assertFalse(t.isExpired()); + t.markTicketExpired(); + assertTrue(t.isExpired()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/UnrecognizableServiceForServiceTicketValidationExceptionTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/UnrecognizableServiceForServiceTicketValidationExceptionTests.java new file mode 100644 index 000000000000..808639a998d5 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/UnrecognizableServiceForServiceTicketValidationExceptionTests.java @@ -0,0 +1,46 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.ticket; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.principal.Service; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +/** + * Test cases for {@link UnrecognizableServiceForServiceTicketValidationException}. + * @author Misagh Moayyed + * @since 4.1 + */ +public class UnrecognizableServiceForServiceTicketValidationExceptionTests { + + private final Service service = TestUtils.getService(); + + @Test + public void verifyThrowableConstructor() { + final UnrecognizableServiceForServiceTicketValidationException t = + new UnrecognizableServiceForServiceTicketValidationException(this.service); + + assertSame(UnrecognizableServiceForServiceTicketValidationException.CODE, t.getCode()); + assertEquals(this.service, t.getOriginalService()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/proxy/support/Cas10ProxyHandlerTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/proxy/support/Cas10ProxyHandlerTests.java new file mode 100644 index 000000000000..69dd3a6b37cd --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/proxy/support/Cas10ProxyHandlerTests.java @@ -0,0 +1,47 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.proxy.support; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.proxy.ProxyHandler; +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class Cas10ProxyHandlerTests { + + private final ProxyHandler proxyHandler = new Cas10ProxyHandler(); + + @Test + public void verifyNoCredentialsOrProxy() { + assertNull(this.proxyHandler.handle(null, null)); + } + + @Test + public void verifyCredentialsAndProxy() { + final TicketGrantingTicket proxyGrantingTicket = mock(TicketGrantingTicket.class); + when(proxyGrantingTicket.getId()).thenReturn("proxyGrantingTicket"); + assertNull(this.proxyHandler.handle(TestUtils.getCredentialsWithSameUsernameAndPassword(), proxyGrantingTicket)); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/proxy/support/Cas20ProxyHandlerTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/proxy/support/Cas20ProxyHandlerTests.java new file mode 100644 index 000000000000..a24a8dc77c30 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/proxy/support/Cas20ProxyHandlerTests.java @@ -0,0 +1,86 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.proxy.support; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.HttpBasedServiceCredential; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.http.HttpClient; +import org.jasig.cas.util.http.SimpleHttpClientFactoryBean; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.net.URL; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Scott Battaglia + + * @since 3.0.0 + */ +public class Cas20ProxyHandlerTests { + + private Cas20ProxyHandler handler; + + @Mock + private TicketGrantingTicket proxyGrantingTicket; + + public Cas20ProxyHandlerTests() { + MockitoAnnotations.initMocks(this); + } + @Before + public void setUp() throws Exception { + this.handler = new Cas20ProxyHandler(); + + final SimpleHttpClientFactoryBean factory = new SimpleHttpClientFactoryBean(); + factory.setConnectionTimeout(10000); + factory.setReadTimeout(10000); + this.handler.setHttpClient(factory.getObject()); + this.handler.setUniqueTicketIdGenerator(new DefaultUniqueTicketIdGenerator()); + when(this.proxyGrantingTicket.getId()).thenReturn("proxyGrantingTicket"); + } + + @Test + public void verifyValidProxyTicketWithoutQueryString() throws Exception { + assertNotNull(this.handler.handle(new HttpBasedServiceCredential( + new URL("https://www.google.com/"), TestUtils.getRegisteredService("https://some.app.edu")), proxyGrantingTicket)); + } + + @Test + public void verifyValidProxyTicketWithQueryString() throws Exception { + assertNotNull(this.handler.handle(new HttpBasedServiceCredential( + new URL("https://www.google.com/?test=test"), TestUtils.getRegisteredService("https://some.app.edu")), + proxyGrantingTicket)); + } + + @Test + public void verifyNonValidProxyTicket() throws Exception { + final SimpleHttpClientFactoryBean clientFactory = new SimpleHttpClientFactoryBean(); + clientFactory.setAcceptableCodes(new int[] {900}); + final HttpClient httpClient = clientFactory.getObject(); + this.handler.setHttpClient(httpClient); + assertNull(this.handler.handle(new HttpBasedServiceCredential(new URL( + "http://www.rutgers.edu"), TestUtils.getRegisteredService("https://some.app.edu")), proxyGrantingTicket)); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/AbstractRegistryCleanerTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/AbstractRegistryCleanerTests.java new file mode 100644 index 000000000000..3b14a2971040 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/AbstractRegistryCleanerTests.java @@ -0,0 +1,91 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collection; + +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public abstract class AbstractRegistryCleanerTests { + protected TicketRegistry ticketRegistry; + + private RegistryCleaner registryCleaner; + + + @Before + public void setUp() throws Exception { + this.ticketRegistry = this.getNewTicketRegistry(); + this.registryCleaner = this.getNewRegistryCleaner(this.ticketRegistry); + } + + public abstract RegistryCleaner getNewRegistryCleaner(TicketRegistry newTicketRegistry); + + public abstract TicketRegistry getNewTicketRegistry(); + + @Test + public void verifyCleanEmptyTicketRegistry() { + clean(); + assertTrue(this.ticketRegistry.getTickets().isEmpty()); + } + + @Test + public void verifyCleanRegistryOfExpiredTicketsAllExpired() { + populateRegistryWithExpiredTickets(); + clean(); + assertTrue(this.ticketRegistry.getTickets().isEmpty()); + } + + @Test + public void verifyCleanRegistryOneNonExpired() { + populateRegistryWithExpiredTickets(); + final TicketGrantingTicket ticket = new TicketGrantingTicketImpl("testNoExpire", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy()); + this.ticketRegistry.addTicket(ticket); + clean(); + assertEquals(this.ticketRegistry.getTickets().size(), 1); + } + + protected void populateRegistryWithExpiredTickets() { + for (int i = 0; i < 10; i++) { + final TicketGrantingTicket ticket = new TicketGrantingTicketImpl("test" + i, TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy()); + ticket.markTicketExpired(); + this.ticketRegistry.addTicket(ticket); + } + } + + private void clean() { + beforeCleaning(); + afterCleaning(this.registryCleaner.clean()); + } + protected void beforeCleaning() {} + protected void afterCleaning(final Collection removedCol) {} +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/AbstractTicketRegistryTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/AbstractTicketRegistryTests.java new file mode 100644 index 000000000000..a719e55a2f02 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/AbstractTicketRegistryTests.java @@ -0,0 +1,217 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.Collection; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public abstract class AbstractTicketRegistryTests { + + private static final int TICKETS_IN_REGISTRY = 10; + + private TicketRegistry ticketRegistry; + + @Before + public void setUp() throws Exception { + this.ticketRegistry = this.getNewTicketRegistry(); + } + + /** + * Abstract method to retrieve a new ticket registry. Implementing classes + * return the TicketRegistry they wish to test. + * + * @return the TicketRegistry we wish to test + * @throws Exception the exception + */ + public abstract TicketRegistry getNewTicketRegistry() throws Exception; + + /** + * Method to add a TicketGrantingTicket to the ticket cache. This should add + * the ticket and return. Failure upon any exception. + */ + @Test + public void verifyAddTicketToCache() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + } catch (final Exception e) { + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyGetNullTicket() { + try { + this.ticketRegistry.getTicket(null, TicketGrantingTicket.class); + } catch (final Exception e) { + fail("Exception caught. None expected."); + } + } + + @Test + public void verifyGetNonExistingTicket() { + try { + this.ticketRegistry.getTicket("FALALALALALAL", TicketGrantingTicket.class); + } catch (final Exception e) { + fail("Exception caught. None expected."); + } + } + + @Test + public void verifyGetExistingTicketWithProperClass() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + this.ticketRegistry.getTicket("TEST", TicketGrantingTicket.class); + } catch (final Exception e) { + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyGetExistingTicketWithInproperClass() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + this.ticketRegistry.getTicket("TEST", ServiceTicket.class); + } catch (final ClassCastException e) { + return; + } + fail("ClassCastfinal Exception expected."); + } + + @Test + public void verifyGetNullTicketWithoutClass() { + try { + this.ticketRegistry.getTicket(null); + } catch (final Exception e) { + fail("Exception caught. None expected."); + } + } + + @Test + public void verifyGetNonExistingTicketWithoutClass() { + try { + this.ticketRegistry.getTicket("FALALALALALAL"); + } catch (final Exception e) { + fail("Exception caught. None expected."); + } + } + + @Test + public void verifyGetExistingTicket() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + this.ticketRegistry.getTicket("TEST"); + } catch (final Exception e) { + e.printStackTrace(); + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyDeleteExistingTicket() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + assertTrue("Ticket was not deleted.", this.ticketRegistry.deleteTicket("TEST")); + } catch (final Exception e) { + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyDeleteNonExistingTicket() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + assertFalse("Ticket was deleted.", this.ticketRegistry.deleteTicket("TEST1")); + } catch (final Exception e) { + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyDeleteNullTicket() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + assertFalse("Ticket was deleted.", this.ticketRegistry.deleteTicket(null)); + } catch (final Exception e) { + e.printStackTrace(); + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyGetTicketsIsZero() { + try { + assertEquals("The size of the empty registry is not zero.", this.ticketRegistry.getTickets().size(), 0); + } catch (final Exception e) { + e.printStackTrace(); + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyGetTicketsFromRegistryEqualToTicketsAdded() { + final Collection tickets = new ArrayList<>(); + + for (int i = 0; i < TICKETS_IN_REGISTRY; i++) { + final TicketGrantingTicket ticketGrantingTicket = new TicketGrantingTicketImpl("TEST" + i, + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + final ServiceTicket st = ticketGrantingTicket.grantServiceTicket("tests" + i, TestUtils.getService(), + new NeverExpiresExpirationPolicy(), false); + tickets.add(ticketGrantingTicket); + tickets.add(st); + this.ticketRegistry.addTicket(ticketGrantingTicket); + this.ticketRegistry.addTicket(st); + } + + try { + final Collection ticketRegistryTickets = this.ticketRegistry.getTickets(); + assertEquals("The size of the registry is not the same as the collection.", ticketRegistryTickets.size(), + tickets.size()); + + for (final Ticket ticket : tickets) { + if (!ticketRegistryTickets.contains(ticket)) { + fail("Ticket was added to registry but was not found in retrieval of collection of all tickets."); + } + } + } catch (final Exception e) { + fail("Caught an exception. But no exception should have been thrown."); + } + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/DefaultTicketRegistryTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/DefaultTicketRegistryTests.java new file mode 100644 index 000000000000..cf60506d0742 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/DefaultTicketRegistryTests.java @@ -0,0 +1,39 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import static org.junit.Assert.*; + +/** + * Test case to test the DefaultTicketRegistry based on test cases to test all + * Ticket Registries. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public class DefaultTicketRegistryTests extends AbstractTicketRegistryTests { + + public TicketRegistry getNewTicketRegistry() throws Exception { + return new DefaultTicketRegistry(); + } + + public void verifyOtherConstructor() { + assertNotNull(new DefaultTicketRegistry(10, 10F, 5)); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/DistributedTicketRegistryTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/DistributedTicketRegistryTests.java new file mode 100644 index 000000000000..5f04ed64d1d9 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/DistributedTicketRegistryTests.java @@ -0,0 +1,154 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +/** + * + * @author Scott Battaglia + * @since 3.1 + * + */ +public final class DistributedTicketRegistryTests { + + private TestDistributedTicketRegistry ticketRegistry; + + private boolean wasTicketUpdated; + + public void setWasTicketUpdated(final boolean wasTicketUpdated) { + this.wasTicketUpdated = wasTicketUpdated; + } + + @Before + public void setUp() throws Exception { + this.ticketRegistry = new TestDistributedTicketRegistry(this); + this.wasTicketUpdated = false; + } + + @Test + public void verifyProxiedInstancesEqual() { + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy()); + this.ticketRegistry.addTicket(t); + + final TicketGrantingTicket returned = (TicketGrantingTicket) this.ticketRegistry.getTicket("test"); + assertEquals(t, returned); + assertEquals(returned, t); + + assertEquals(t.getCreationTime(), returned.getCreationTime()); + assertEquals(t.getAuthentication(), returned.getAuthentication()); + assertEquals(t.getCountOfUses(), returned.getCountOfUses()); + assertEquals(t.getGrantingTicket(), returned.getGrantingTicket()); + assertEquals(t.getId(), returned.getId()); + assertEquals(t.getChainedAuthentications(), returned.getChainedAuthentications()); + assertEquals(t.isExpired(), returned.isExpired()); + assertEquals(t.isRoot(), returned.isRoot()); + + final ServiceTicket s = t.grantServiceTicket("stest", TestUtils.getService(), + new NeverExpiresExpirationPolicy(), false); + this.ticketRegistry.addTicket(s); + + final ServiceTicket sreturned = (ServiceTicket) this.ticketRegistry.getTicket("stest"); + assertEquals(s, sreturned); + assertEquals(sreturned, s); + + assertEquals(s.getCreationTime(), sreturned.getCreationTime()); + assertEquals(s.getCountOfUses(), sreturned.getCountOfUses()); + assertEquals(s.getGrantingTicket(), sreturned.getGrantingTicket()); + assertEquals(s.getId(), sreturned.getId()); + assertEquals(s.isExpired(), sreturned.isExpired()); + assertEquals(s.getService(), sreturned.getService()); + assertEquals(s.isFromNewLogin(), sreturned.isFromNewLogin()); + } + + @Test + public void verifyUpdateOfRegistry() { + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy()); + this.ticketRegistry.addTicket(t); + final TicketGrantingTicket returned = (TicketGrantingTicket) this.ticketRegistry.getTicket("test"); + + final ServiceTicket s = returned.grantServiceTicket("test2", TestUtils.getService(), + new NeverExpiresExpirationPolicy(), true); + + this.ticketRegistry.addTicket(s); + final ServiceTicket s2 = (ServiceTicket) this.ticketRegistry.getTicket("test2"); + assertNotNull(s2.grantTicketGrantingTicket("ff", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + + assertTrue(s2.isValidFor(TestUtils.getService())); + assertTrue(this.wasTicketUpdated); + + returned.markTicketExpired(); + assertTrue(t.isExpired()); + } + + @Test + public void verifyTicketDoesntExist() { + assertNull(this.ticketRegistry.getTicket("fdfas")); + } + + private static class TestDistributedTicketRegistry extends AbstractDistributedTicketRegistry { + private final DistributedTicketRegistryTests parent; + private final Map tickets = new HashMap<>(); + + public TestDistributedTicketRegistry(final DistributedTicketRegistryTests parent) { + this.parent = parent; + } + + protected void updateTicket(final Ticket ticket) { + this.parent.setWasTicketUpdated(true); + } + + public void addTicket(final Ticket ticket) { + this.tickets.put(ticket.getId(), ticket); + } + + public boolean deleteTicket(final String ticketId) { + return this.tickets.remove(ticketId) != null; + } + + public Ticket getTicket(final String ticketId) { + return getProxiedTicketInstance(this.tickets.get(ticketId)); + } + + public Collection getTickets() { + return this.tickets.values(); + } + + @Override + protected boolean needsCallback() { + return true; + } + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/support/DefaultTicketRegistryCleanerTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/support/DefaultTicketRegistryCleanerTests.java new file mode 100644 index 000000000000..8bd1f19b5963 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/support/DefaultTicketRegistryCleanerTests.java @@ -0,0 +1,68 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry.support; + +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.CentralAuthenticationServiceImpl; +import org.jasig.cas.authentication.AuthenticationManager; +import org.jasig.cas.logout.LogoutManager; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.registry.AbstractRegistryCleanerTests; +import org.jasig.cas.ticket.registry.DefaultTicketRegistry; +import org.jasig.cas.ticket.registry.RegistryCleaner; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.jasig.cas.util.UniqueTicketIdGenerator; + +import java.util.Collection; +import java.util.Collections; + +import static org.mockito.Mockito.*; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class DefaultTicketRegistryCleanerTests extends AbstractRegistryCleanerTests { + + private CentralAuthenticationService centralAuthenticationService; + + @Override + public RegistryCleaner getNewRegistryCleaner(final TicketRegistry ticketRegistry) { + this.centralAuthenticationService = new CentralAuthenticationServiceImpl(this.ticketRegistry, this.ticketRegistry, + mock(AuthenticationManager.class), mock(UniqueTicketIdGenerator.class), Collections.EMPTY_MAP, + new NeverExpiresExpirationPolicy(), new NeverExpiresExpirationPolicy(), mock(ServicesManager.class), + mock(LogoutManager.class)); + + return new DefaultTicketRegistryCleaner(this.centralAuthenticationService, this.ticketRegistry); + } + + @Override + public TicketRegistry getNewTicketRegistry() { + return new DefaultTicketRegistry(); + } + + @Override + protected void afterCleaning(final Collection removedCol) { + for (final Ticket ticket : removedCol) { + this.ticketRegistry.deleteTicket(ticket.getId()); + } + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/support/MultiTimeUseOrTimeoutExpirationPolicyTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/support/MultiTimeUseOrTimeoutExpirationPolicyTests.java new file mode 100644 index 000000000000..3e6ffa76d333 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/support/MultiTimeUseOrTimeoutExpirationPolicyTests.java @@ -0,0 +1,80 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.support; + +import static org.junit.Assert.*; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.TimeUnit; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class MultiTimeUseOrTimeoutExpirationPolicyTests { + + private static final long TIMEOUT_MILLISECONDS = 100L; + + private static final int NUMBER_OF_USES = 5; + + private static final int TIMEOUT_BUFFER = 50; + + private ExpirationPolicy expirationPolicy; + + private TicketGrantingTicket ticket; + + @Before + public void setUp() throws Exception { + this.expirationPolicy = new MultiTimeUseOrTimeoutExpirationPolicy(NUMBER_OF_USES, TIMEOUT_MILLISECONDS, + TimeUnit.MILLISECONDS); + + this.ticket = new TicketGrantingTicketImpl("test", TestUtils.getAuthentication(), this.expirationPolicy); + + } + + @Test + public void verifyTicketIsNull() { + assertTrue(this.expirationPolicy.isExpired(null)); + } + + @Test + public void verifyTicketIsNotExpired() { + assertFalse(this.ticket.isExpired()); + } + + @Test + public void verifyTicketIsExpiredByTime() throws InterruptedException { + Thread.sleep(TIMEOUT_MILLISECONDS + TIMEOUT_BUFFER); + assertTrue(this.ticket.isExpired()); + } + + @Test + public void verifyTicketIsExpiredByCount() { + for (int i = 0; i < NUMBER_OF_USES; i++) { + this.ticket.grantServiceTicket("test", TestUtils.getService(), new NeverExpiresExpirationPolicy(), false); + } + assertTrue(this.ticket.isExpired()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/support/RememberMeDelegatingExpirationPolicyTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/support/RememberMeDelegatingExpirationPolicyTests.java new file mode 100644 index 000000000000..1d2ee02367e2 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/support/RememberMeDelegatingExpirationPolicyTests.java @@ -0,0 +1,81 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.RememberMeCredential; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.jasig.cas.authentication.principal.PrincipalFactory; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.junit.Before; +import org.junit.Test; + +import javax.validation.constraints.NotNull; +import java.util.Collections; + +import static org.junit.Assert.*; + +/** + * Tests for RememberMeDelegatingExpirationPolicy. + * + * @author Scott Battaglia + * @since 3.2.1 + * + */ +public final class RememberMeDelegatingExpirationPolicyTests { + + /** Factory to create the principal type. **/ + @NotNull + protected PrincipalFactory principalFactory = new DefaultPrincipalFactory(); + + + private RememberMeDelegatingExpirationPolicy p; + + @Before + public void setUp() throws Exception { + this.p = new RememberMeDelegatingExpirationPolicy(); + this.p.setRememberMeExpirationPolicy(new MultiTimeUseOrTimeoutExpirationPolicy(1, 20000)); + this.p.setSessionExpirationPolicy(new MultiTimeUseOrTimeoutExpirationPolicy(5, 20000)); + } + + @Test + public void verifyTicketExpirationWithRememberMe() { + final Authentication authentication = TestUtils.getAuthentication( + this.principalFactory.createPrincipal("test"), + Collections.singletonMap( + RememberMeCredential.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME, true)); + final TicketGrantingTicketImpl t = new TicketGrantingTicketImpl("111", authentication, this.p); + assertFalse(t.isExpired()); + t.grantServiceTicket("55", TestUtils.getService(), this.p, false); + assertTrue(t.isExpired()); + + } + + @Test + public void verifyTicketExpirationWithoutRememberMe() { + final Authentication authentication = TestUtils.getAuthentication(); + final TicketGrantingTicketImpl t = new TicketGrantingTicketImpl("111", authentication, this.p); + assertFalse(t.isExpired()); + t.grantServiceTicket("55", TestUtils.getService(), this.p, false); + assertFalse(t.isExpired()); + + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/support/ThrottledUseAndTimeoutExpirationPolicyTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/support/ThrottledUseAndTimeoutExpirationPolicyTests.java new file mode 100644 index 000000000000..2ae4000a1dcf --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/support/ThrottledUseAndTimeoutExpirationPolicyTests.java @@ -0,0 +1,77 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.support; + +import static org.junit.Assert.*; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class ThrottledUseAndTimeoutExpirationPolicyTests { + + private static final long TIMEOUT = 50; + + private static final long TIMEOUT_BUFFER = 10; + + private ThrottledUseAndTimeoutExpirationPolicy expirationPolicy; + + private TicketGrantingTicket ticket; + + @Before + public void setUp() throws Exception { + this.expirationPolicy = new ThrottledUseAndTimeoutExpirationPolicy(); + this.expirationPolicy.setTimeToKillInMilliSeconds(TIMEOUT); + this.expirationPolicy.setTimeInBetweenUsesInMilliSeconds(TIMEOUT / 5); + + this.ticket = new TicketGrantingTicketImpl("test", TestUtils + .getAuthentication(), this.expirationPolicy); + + } + + @Test + public void verifyTicketIsNotExpired() { + assertFalse(this.ticket.isExpired()); + } + + @Test + public void verifyTicketIsExpired() throws InterruptedException { + Thread.sleep(TIMEOUT + TIMEOUT_BUFFER); + assertTrue(this.ticket.isExpired()); + } + + @Test + public void verifyTicketUsedButWithTimeout() throws InterruptedException { + this.ticket.grantServiceTicket("test", TestUtils.getService(), this.expirationPolicy, false); + Thread.sleep(TIMEOUT - TIMEOUT_BUFFER); + assertFalse(this.ticket.isExpired()); + } + + @Test + public void verifyNotWaitingEnoughTime() { + this.ticket.grantServiceTicket("test", TestUtils.getService(), this.expirationPolicy, false); + assertTrue(this.ticket.isExpired()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/support/TicketGrantingTicketExpirationPolicyTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/support/TicketGrantingTicketExpirationPolicyTests.java new file mode 100644 index 000000000000..87d74534e49f --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/support/TicketGrantingTicketExpirationPolicyTests.java @@ -0,0 +1,87 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.support; + +import static org.junit.Assert.*; +import org.jasig.cas.TestUtils; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.junit.Before; +import org.junit.Test; + +/** + * @author William G. Thompson, Jr. + + * @since 3.4.10 + */ +public class TicketGrantingTicketExpirationPolicyTests { + + private static final long HARD_TIMEOUT = 100L; + + private static final long SLIDING_TIMEOUT = 60L; + + private static final long TIMEOUT_BUFFER = 20L; // needs to be long enough for timeouts to be triggered + + private TicketGrantingTicketExpirationPolicy expirationPolicy; + + private TicketGrantingTicket ticketGrantingTicket; + + @Before + public void setUp() throws Exception { + this.expirationPolicy = new TicketGrantingTicketExpirationPolicy(); + this.expirationPolicy.setMaxTimeToLiveInMilliSeconds(HARD_TIMEOUT); + this.expirationPolicy.setTimeToKillInMilliSeconds(SLIDING_TIMEOUT); + this.ticketGrantingTicket = new TicketGrantingTicketImpl("test", TestUtils.getAuthentication(), + this.expirationPolicy); + } + + @Test + public void verifyTgtIsExpiredByHardTimeOut() throws InterruptedException { + // keep tgt alive via sliding window until within SLIDING_TIME / 2 of the HARD_TIMEOUT + while (System.currentTimeMillis() - ticketGrantingTicket.getCreationTime() + < (HARD_TIMEOUT - SLIDING_TIMEOUT / 2)) { + ticketGrantingTicket.grantServiceTicket("test", TestUtils.getService(), expirationPolicy, false); + Thread.sleep(SLIDING_TIMEOUT - TIMEOUT_BUFFER); + assertFalse(this.ticketGrantingTicket.isExpired()); + } + + // final sliding window extension past the HARD_TIMEOUT + ticketGrantingTicket.grantServiceTicket("test", TestUtils.getService(), expirationPolicy, false); + Thread.sleep(SLIDING_TIMEOUT / 2 + TIMEOUT_BUFFER); + assertTrue(ticketGrantingTicket.isExpired()); + + } + + @Test + public void verifyTgtIsExpiredBySlidingWindow() throws InterruptedException { + ticketGrantingTicket.grantServiceTicket("test", TestUtils.getService(), expirationPolicy, false); + Thread.sleep(SLIDING_TIMEOUT - TIMEOUT_BUFFER); + assertFalse(ticketGrantingTicket.isExpired()); + + ticketGrantingTicket.grantServiceTicket("test", TestUtils.getService(), expirationPolicy, false); + Thread.sleep(SLIDING_TIMEOUT - TIMEOUT_BUFFER); + assertFalse(ticketGrantingTicket.isExpired()); + + ticketGrantingTicket.grantServiceTicket("test", TestUtils.getService(), expirationPolicy, false); + Thread.sleep(SLIDING_TIMEOUT + TIMEOUT_BUFFER); + assertTrue(ticketGrantingTicket.isExpired()); + + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/support/TimeoutExpirationPolicyTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/support/TimeoutExpirationPolicyTests.java new file mode 100644 index 000000000000..4aa5a67e2ad8 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/support/TimeoutExpirationPolicyTests.java @@ -0,0 +1,68 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.support; + +import static org.junit.Assert.*; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.junit.Before; +import org.junit.Test; + + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class TimeoutExpirationPolicyTests { + + private static final long TIMEOUT = 50; + + private ExpirationPolicy expirationPolicy; + + private Ticket ticket; + + @Before + public void setUp() throws Exception { + this.expirationPolicy = new TimeoutExpirationPolicy(TIMEOUT); + + this.ticket = new TicketGrantingTicketImpl("test", TestUtils + .getAuthentication(), this.expirationPolicy); + + } + + @Test + public void verifyTicketIsNull() { + assertTrue(this.expirationPolicy.isExpired(null)); + } + + @Test + public void verifyTicketIsNotExpired() { + assertFalse(this.ticket.isExpired()); + } + + @Test + public void verifyTicketIsExpired() throws InterruptedException { + Thread.sleep(TIMEOUT + 10); // this failed when it was only +1...not + // accurate?? + assertTrue(this.ticket.isExpired()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/util/DefaultCipherExecutorTests.java b/cas-server-core/src/test/java/org/jasig/cas/util/DefaultCipherExecutorTests.java new file mode 100644 index 000000000000..991e7e27ccac --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/util/DefaultCipherExecutorTests.java @@ -0,0 +1,40 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.util; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Test cases for {@link org.jasig.cas.util.DefaultCipherExecutor}. + * + * @author Misagh Moayyed + * @since 4.1 + */ +public class DefaultCipherExecutorTests { + + @Test + public void checkEncryptionWithDefaultSettings() { + final CipherExecutor cipherExecutor = new DefaultCipherExecutor("1PbwSbnHeinpkZOSZjuSJ8yYpUrInm5aaV18J2Ar4rM", + "szxK-5_eJjs-aUj-64MpUZ-GPPzGLhYPLGl0wrYjYNVAGva2P0lLe6UGKGM7k8dWxsOVGutZWgvmY3l5oVPO3w"); + assertEquals(cipherExecutor.decode(cipherExecutor.encode("CAS Test")), "CAS Test"); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/util/DefaultLongNumericGeneratorTests.java b/cas-server-core/src/test/java/org/jasig/cas/util/DefaultLongNumericGeneratorTests.java new file mode 100644 index 000000000000..6f4677269abc --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/util/DefaultLongNumericGeneratorTests.java @@ -0,0 +1,65 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class DefaultLongNumericGeneratorTests { + + @Test + public void verifyWrap() { + assertEquals(Long.MAX_VALUE, new DefaultLongNumericGenerator(Long.MAX_VALUE) + .getNextLong()); + } + + @Test + public void verifyInitialValue() { + assertEquals(10L, new DefaultLongNumericGenerator(10L) + .getNextLong()); + } + + @Test + public void verifyIncrementWithNoWrap() { + assertEquals(0, new DefaultLongNumericGenerator().getNextLong()); + } + + @Test + public void verifyIncrementWithNoWrap2() { + final DefaultLongNumericGenerator g = new DefaultLongNumericGenerator(); + g.getNextLong(); + assertEquals(1, g.getNextLong()); + } + + @Test + public void verifyMinimumSize() { + assertEquals(1, new DefaultLongNumericGenerator().minLength()); + } + + @Test + public void verifyMaximumLength() { + assertEquals(Long.toString(Long.MAX_VALUE).length(), + new DefaultLongNumericGenerator().maxLength()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/util/DefaultRandomStringGeneratorTests.java b/cas-server-core/src/test/java/org/jasig/cas/util/DefaultRandomStringGeneratorTests.java new file mode 100644 index 000000000000..dc03403ce6b1 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/util/DefaultRandomStringGeneratorTests.java @@ -0,0 +1,53 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import static org.junit.Assert.*; + +import org.junit.Test; + + +/** + * @author Scott Battaglia + + * @since 3.0.0 + */ +public class DefaultRandomStringGeneratorTests { + + private static final int LENGTH = 35; + + private final RandomStringGenerator randomStringGenerator = new DefaultRandomStringGenerator( + LENGTH); + + @Test + public void verifyMaxLength() { + assertEquals(LENGTH, this.randomStringGenerator.getMaxLength()); + } + + @Test + public void verifyMinLength() { + assertEquals(LENGTH, this.randomStringGenerator.getMinLength()); + } + + @Test + public void verifyRandomString() { + assertNotSame(this.randomStringGenerator.getNewString(), + this.randomStringGenerator.getNewString()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/util/DefaultUniqueTicketIdGeneratorTests.java b/cas-server-core/src/test/java/org/jasig/cas/util/DefaultUniqueTicketIdGeneratorTests.java new file mode 100644 index 000000000000..1d18f76862a6 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/util/DefaultUniqueTicketIdGeneratorTests.java @@ -0,0 +1,48 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + + * @since 3.0.0 + */ +public class DefaultUniqueTicketIdGeneratorTests { + + @Test + public void verifyUniqueGenerationOfTicketIds() { + final DefaultUniqueTicketIdGenerator generator = new DefaultUniqueTicketIdGenerator( + 10); + + assertNotSame(generator.getNewTicketId("TEST"), generator + .getNewTicketId("TEST")); + } + + @Test + public void verifySuffix() { + final String suffix = "suffix"; + final DefaultUniqueTicketIdGenerator generator = new DefaultUniqueTicketIdGenerator(10, suffix); + + assertTrue(generator.getNewTicketId("test").endsWith(suffix)); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/util/HostNameBasedUniqueTicketIdGeneratorTests.java b/cas-server-core/src/test/java/org/jasig/cas/util/HostNameBasedUniqueTicketIdGeneratorTests.java new file mode 100644 index 000000000000..472330366661 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/util/HostNameBasedUniqueTicketIdGeneratorTests.java @@ -0,0 +1,39 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Handles tests for {@link org.jasig.cas.util.HostNameBasedUniqueTicketIdGenerator}. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class HostNameBasedUniqueTicketIdGeneratorTests { + + @Test + public void verifyUniqueGenerationOfTicketIds() throws Exception { + final HostNameBasedUniqueTicketIdGenerator generator = new HostNameBasedUniqueTicketIdGenerator(10); + final String id1 = generator.getNewTicketId("TEST"); + final String id2 = generator.getNewTicketId("TEST"); + assertNotSame(id1, id2); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/util/TrustedProxyAuthenticationTrustStoreSslSocketFactoryTests.java b/cas-server-core/src/test/java/org/jasig/cas/util/TrustedProxyAuthenticationTrustStoreSslSocketFactoryTests.java new file mode 100644 index 000000000000..22964b8a845b --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/util/TrustedProxyAuthenticationTrustStoreSslSocketFactoryTests.java @@ -0,0 +1,63 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import org.jasig.cas.authentication.FileTrustStoreSslSocketFactory; +import org.jasig.cas.util.http.HttpClient; +import org.jasig.cas.util.http.SimpleHttpClientFactoryBean; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; + +import static org.junit.Assert.assertTrue; + +/** + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class TrustedProxyAuthenticationTrustStoreSslSocketFactoryTests { + private static final ClassPathResource TRUST_STORE = new ClassPathResource("truststore.jks"); + private static final String TRUST_STORE_PSW = "changeit"; + + private HttpClient client; + + @Before + public void prepareHttpClient() throws Exception { + final FileTrustStoreSslSocketFactory sslFactory = new FileTrustStoreSslSocketFactory( + TRUST_STORE.getFile(), TRUST_STORE_PSW); + + final SimpleHttpClientFactoryBean clientFactory = new SimpleHttpClientFactoryBean(); + clientFactory.setSslSocketFactory(sslFactory); + this.client = clientFactory.getObject(); + } + + @Ignore + public void verifySuccessfulConnection() { + final boolean valid = client.isValidEndPoint("https://www.github.com"); + assertTrue(valid); + } + + @Test + public void verifySuccessfulConnectionWithCustomSSLCert() { + final boolean valid = client.isValidEndPoint("https://www.cacert.org"); + assertTrue(valid); + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/util/http/HttpMessageTests.java b/cas-server-core/src/test/java/org/jasig/cas/util/http/HttpMessageTests.java new file mode 100644 index 000000000000..cbde7be08dca --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/util/http/HttpMessageTests.java @@ -0,0 +1,42 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util.http; + +import static org.junit.Assert.*; + +import java.net.URL; + +import org.junit.Test; + +/** + * + * @author Francesco Cina + * @since 4.1 + */ +public class HttpMessageTests { + + @Test + public void verifyAsyncArgIsTakenIntoAccount() throws Exception { + assertTrue(new HttpMessage(new URL("http://www.google.com"), "messageToSend").isAsynchronous()); + assertTrue(new HttpMessage(new URL("http://www.google.com"), "messageToSend", true).isAsynchronous()); + assertFalse(new HttpMessage(new URL("http://www.google.com"), "messageToSend", false).isAsynchronous()); + } + +} + diff --git a/cas-server-core/src/test/java/org/jasig/cas/util/http/SimpleHttpClientTests.java b/cas-server-core/src/test/java/org/jasig/cas/util/http/SimpleHttpClientTests.java new file mode 100644 index 000000000000..c2c663cad373 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/util/http/SimpleHttpClientTests.java @@ -0,0 +1,84 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util.http; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.junit.Test; + +/** + * Test cases for {@link SimpleHttpClient}. + * @author Scott Battaglia + * @since 3.1 + */ +public class SimpleHttpClientTests { + + private SimpleHttpClient getHttpClient() throws Exception { + final SimpleHttpClient httpClient = new SimpleHttpClientFactoryBean().getObject(); + return httpClient; + } + + @Test + public void verifyOkayUrl() throws Exception { + assertTrue(this.getHttpClient().isValidEndPoint("http://www.google.com")); + } + + @Test + public void verifyBadUrl() throws Exception { + assertFalse(this.getHttpClient().isValidEndPoint("https://www.abc1234.org")); + } + + @Test + public void verifyInvalidHttpsUrl() throws Exception { + final HttpClient client = this.getHttpClient(); + assertFalse(client.isValidEndPoint("https://static.ak.connect.facebook.com")); + } + + @Test + public void verifyBypassedInvalidHttpsUrl() throws Exception { + final SimpleHttpClientFactoryBean clientFactory = new SimpleHttpClientFactoryBean(); + clientFactory.setSslSocketFactory(getFriendlyToAllSSLSocketFactory()); + clientFactory.setHostnameVerifier(new NoopHostnameVerifier()); + clientFactory.setAcceptableCodes(new int[] {200, 403}); + final SimpleHttpClient client = clientFactory.getObject(); + assertTrue(client.isValidEndPoint("https://static.ak.connect.facebook.com")); + } + + private SSLConnectionSocketFactory getFriendlyToAllSSLSocketFactory() throws Exception { + final TrustManager trm = new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return null; + } + public void checkClientTrusted(final X509Certificate[] certs, final String authType) {} + public void checkServerTrusted(final X509Certificate[] certs, final String authType) {} + }; + final SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, new TrustManager[] {trm}, null); + return new SSLConnectionSocketFactory(sc, new NoopHostnameVerifier()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/validation/Cas10ProtocolValidationSpecificationTests.java b/cas-server-core/src/test/java/org/jasig/cas/validation/Cas10ProtocolValidationSpecificationTests.java new file mode 100644 index 000000000000..92447916054a --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/validation/Cas10ProtocolValidationSpecificationTests.java @@ -0,0 +1,73 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.validation; + +import static org.junit.Assert.*; + +import org.jasig.cas.TestUtils; +import org.junit.Test; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class Cas10ProtocolValidationSpecificationTests { + + @Test + public void verifyRenewGettersAndSettersFalse() { + final Cas10ProtocolValidationSpecification s = new Cas10ProtocolValidationSpecification(); + s.setRenew(false); + assertFalse(s.isRenew()); + } + + @Test + public void verifyRenewGettersAndSettersTrue() { + final Cas10ProtocolValidationSpecification s = new Cas10ProtocolValidationSpecification(); + s.setRenew(true); + assertTrue(s.isRenew()); + } + + @Test + public void verifyRenewAsTrueAsConstructor() { + assertTrue(new Cas10ProtocolValidationSpecification(true).isRenew()); + } + + @Test + public void verifyRenewAsFalseAsConstructor() { + assertFalse(new Cas10ProtocolValidationSpecification(false).isRenew()); + } + + @Test + public void verifySatisfiesSpecOfTrue() { + assertTrue(new Cas10ProtocolValidationSpecification(true).isSatisfiedBy(TestUtils.getAssertion(true))); + } + + public void verifyNotSatisfiesSpecOfTrue() { + assertFalse(new Cas10ProtocolValidationSpecification(true).isSatisfiedBy(TestUtils.getAssertion(false))); + } + + public void verifySatisfiesSpecOfFalse() { + assertTrue(new Cas10ProtocolValidationSpecification(false).isSatisfiedBy(TestUtils.getAssertion(true))); + } + + public void verifySatisfiesSpecOfFalse2() { + assertTrue(new Cas10ProtocolValidationSpecification(false).isSatisfiedBy(TestUtils.getAssertion(false))); + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/validation/Cas20ProtocolValidationSpecificationTests.java b/cas-server-core/src/test/java/org/jasig/cas/validation/Cas20ProtocolValidationSpecificationTests.java new file mode 100644 index 000000000000..8bac9ce3e5d5 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/validation/Cas20ProtocolValidationSpecificationTests.java @@ -0,0 +1,75 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.validation; + +import static org.junit.Assert.*; + +import org.jasig.cas.TestUtils; +import org.junit.Test; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class Cas20ProtocolValidationSpecificationTests { + + @Test + public void verifyRenewGettersAndSettersFalse() { + final Cas20ProtocolValidationSpecification s = new Cas20ProtocolValidationSpecification(); + s.setRenew(false); + assertFalse(s.isRenew()); + } + + @Test + public void verifyRenewGettersAndSettersTrue() { + final Cas20ProtocolValidationSpecification s = new Cas20ProtocolValidationSpecification(); + s.setRenew(true); + assertTrue(s.isRenew()); + } + + @Test + public void verifyRenewAsTrueAsConstructor() { + assertTrue(new Cas20ProtocolValidationSpecification(true).isRenew()); + } + + @Test + public void verifyRenewAsFalseAsConstructor() { + assertFalse(new Cas20ProtocolValidationSpecification(false).isRenew()); + } + + @Test + public void verifySatisfiesSpecOfTrue() { + assertTrue(new Cas20ProtocolValidationSpecification(true).isSatisfiedBy(TestUtils.getAssertion(true))); + } + + @Test + public void verifyNotSatisfiesSpecOfTrue() { + assertFalse(new Cas20ProtocolValidationSpecification(true).isSatisfiedBy(TestUtils.getAssertion(false))); + } + + @Test + public void verifySatisfiesSpecOfFalse() { + assertTrue(new Cas20ProtocolValidationSpecification(false).isSatisfiedBy(TestUtils.getAssertion(true))); + } + + @Test + public void verifySatisfiesSpecOfFalse2() { + assertTrue(new Cas20ProtocolValidationSpecification(false).isSatisfiedBy(TestUtils.getAssertion(false))); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/validation/Cas20WithoutProxyingValidationSpecificationTests.java b/cas-server-core/src/test/java/org/jasig/cas/validation/Cas20WithoutProxyingValidationSpecificationTests.java new file mode 100644 index 000000000000..076930ec7973 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/validation/Cas20WithoutProxyingValidationSpecificationTests.java @@ -0,0 +1,67 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.validation; + +import static org.junit.Assert.*; + +import org.jasig.cas.TestUtils; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class Cas20WithoutProxyingValidationSpecificationTests { + + private Cas20WithoutProxyingValidationSpecification validationSpecification; + + @Before + public void setUp() throws Exception { + this.validationSpecification = new Cas20WithoutProxyingValidationSpecification(); + } + + @Test + public void verifySatisfiesSpecOfTrue() { + assertTrue(this.validationSpecification.isSatisfiedBy(TestUtils.getAssertion(true))); + } + + @Test + public void verifyNotSatisfiesSpecOfTrue() { + this.validationSpecification.setRenew(true); + assertFalse(this.validationSpecification.isSatisfiedBy(TestUtils.getAssertion(false))); + } + + @Test + public void verifySatisfiesSpecOfFalse() { + assertTrue(this.validationSpecification.isSatisfiedBy(TestUtils.getAssertion(false))); + } + + @Test + public void verifyDoesNotSatisfiesSpecOfFalse() { + assertFalse(this.validationSpecification.isSatisfiedBy(TestUtils.getAssertion(false, new String[] {"test2"}))); + } + + @Test + public void verifySettingRenew() { + final Cas20WithoutProxyingValidationSpecification validation = new Cas20WithoutProxyingValidationSpecification( + true); + assertTrue(validation.isRenew()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/validation/ImmutableAssertionTests.java b/cas-server-core/src/test/java/org/jasig/cas/validation/ImmutableAssertionTests.java new file mode 100644 index 000000000000..b698cbe0d222 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/validation/ImmutableAssertionTests.java @@ -0,0 +1,125 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.validation; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.*; + +/** + * Unit test for {@link org.jasig.cas.validation.ImmutableAssertion} class. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public class ImmutableAssertionTests { + + @Test + public void verifyGettersForChainedPrincipals() { + final List list = new ArrayList<>(); + + list.add(TestUtils.getAuthentication("test")); + list.add(TestUtils.getAuthentication("test1")); + list.add(TestUtils.getAuthentication("test2")); + + final ImmutableAssertion assertion = new ImmutableAssertion( + TestUtils.getAuthentication(), list, TestUtils.getService(), true); + + assertEquals(list.toArray(new Authentication[0]).length, assertion.getChainedAuthentications().size()); + } + + @Test + public void verifyGetterFalseForNewLogin() { + final List list = new ArrayList<>(); + + list.add(TestUtils.getAuthentication()); + + final ImmutableAssertion assertion = new ImmutableAssertion( + TestUtils.getAuthentication(), list, TestUtils.getService(), false); + + assertFalse(assertion.isFromNewLogin()); + } + + @Test + public void verifyGetterTrueForNewLogin() { + final List list = new ArrayList<>(); + + list.add(TestUtils.getAuthentication()); + + final ImmutableAssertion assertion = new ImmutableAssertion( + TestUtils.getAuthentication(), list, TestUtils.getService(), true); + + assertTrue(assertion.isFromNewLogin()); + } + + @Test + public void verifyEqualsWithNull() { + final List list = new ArrayList<>(); + list.add(TestUtils.getAuthentication()); + + final ImmutableAssertion assertion = new ImmutableAssertion( + TestUtils.getAuthentication(), list, TestUtils.getService(), true); + + assertNotEquals(assertion, null); + } + + @Test + public void verifyEqualsWithInvalidObject() { + final List list = new ArrayList<>(); + list.add(TestUtils.getAuthentication()); + + final ImmutableAssertion assertion = new ImmutableAssertion( + TestUtils.getAuthentication(), list, TestUtils.getService(), true); + + assertFalse("test".equals(assertion)); + } + + @Test + public void verifyEqualsWithValidObject() { + final List list1 = new ArrayList<>(); + final List list2 = new ArrayList<>(); + + final Authentication auth = TestUtils.getAuthentication(); + list1.add(auth); + list2.add(auth); + + final ImmutableAssertion assertion1 = new ImmutableAssertion(auth, list1, TestUtils.getService(), true); + final ImmutableAssertion assertion2 = new ImmutableAssertion(auth, list2, TestUtils.getService(), true); + + assertTrue(assertion1.equals(assertion2)); + } + + @Test + public void verifyGetService() { + final Service service = TestUtils.getService(); + + final List list = new ArrayList<>(); + list.add(TestUtils.getAuthentication()); + + final Assertion assertion = new ImmutableAssertion(TestUtils.getAuthentication(), list, service, false); + + assertEquals(service, assertion.getService()); + } +} diff --git a/cas-server-core/src/test/java/org/slf4j/impl/CasLoggerFactoryTests.java b/cas-server-core/src/test/java/org/slf4j/impl/CasLoggerFactoryTests.java new file mode 100644 index 000000000000..56472444b37e --- /dev/null +++ b/cas-server-core/src/test/java/org/slf4j/impl/CasLoggerFactoryTests.java @@ -0,0 +1,369 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.slf4j.impl; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.ticket.InvalidTicketException; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.Marker; +import org.slf4j.helpers.Util; + +import java.io.File; +import java.io.IOException; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class CasLoggerFactoryTests { + + private static final File LOG_FILE = new File("target", "slf4j.log"); + + private static final String ID1 = TicketGrantingTicket.PREFIX + + "-1-B0tjWgMIhUU4kgCZdXbxnWccTFYpTbRbArjaoutXnlNMbIShEu-cas"; + private static final String ID2 = TicketGrantingTicket.PROXY_GRANTING_TICKET_PREFIX + + "-1-B0tjWgMIhUU4kgCZd32xnWccTFYpTbRbArjaoutXnlNMbIShEu-cas"; + private static final String ID3 = TicketGrantingTicket.PROXY_GRANTING_TICKET_IOU_PREFIX + + "-1-B0tjWgMIhUU4kgCZd32xnWccTFYpTbRbArjaoutXnlNMbIShEu-cas"; + private Logger logger; + + @BeforeClass + public static void beforeClass() throws IOException { + if (LOG_FILE.exists()) { + Util.report("Initializing log file " + LOG_FILE.getCanonicalPath()); + FileUtils.write(LOG_FILE, "", false); + } + } + + @After + public void after() throws IOException { + FileUtils.write(LOG_FILE, "", false); + } + + @Before + public void beforeTest() { + logger = LoggerFactory.getLogger(CasLoggerFactoryTests.class); + } + + @Test + public void verifyLoggerSelectedCorrectly() { + assertTrue(logger instanceof CasDelegatingLogger); + } + + @Test + public void verifyLogging1() { + logger.trace(getMarker("trace"), getMessageToLogWithParams(), null, null); + validateLogData(); + } + + @Test + public void verifyLogging2() { + logger.trace(getMarker("trace"), getMessageToLog()); + validateLogData(); + } + + @Test + public void verifyLogging3() { + logger.trace(getMarker("trace"), getMessageToLogWithParams(), ID2, ID1); + validateLogData(); + } + + @Test + public void verifyLogging4() { + logger.trace(getMarker("trace"), getMessageToLogWithParams(), ID2, ID1, ID2); + validateLogData(); + } + + @Test + public void verifyLogging5() { + logger.trace(getMarker("trace"), getMessageToLog(), new RuntimeException(ID1, new InvalidTicketException(ID2))); + validateLogData(); + } + + @Test + public void verifyLogging6() { + logger.trace(getMessageToLog()); + validateLogData(); + } + + @Test + public void verifyLogging7() { + logger.trace(getMessageToLogWithParams(), ID2, ID1); + validateLogData(); + } + + @Test + public void verifyLogging8() { + logger.trace(getMessageToLogWithParams(), ID2, ID1, ID2); + validateLogData(); + } + + @Test + public void verifyLogging9() { + logger.trace(getMessageToLog(), new RuntimeException(ID1, new InvalidTicketException(ID2))); + validateLogData(); + } + + @Test + public void verifyLogging10() { + logger.debug(getMarker("debug"), getMessageToLogWithParams(), ID3, ID1); + validateLogData(); + } + + @Test + public void verifyLogging21() { + logger.debug(getMarker("debug"), getMessageToLog()); + validateLogData(); + } + + @Test + public void verifyLogging31() { + logger.debug(getMarker("debug"), getMessageToLogWithParams(), ID2, ID1); + validateLogData(); + } + + @Test + public void verifyLogging41() { + logger.debug(getMarker("debug"), getMessageToLogWithParams(), ID2, ID1, ID2); + validateLogData(); + } + + @Test + public void verifyLogging51() { + logger.debug(getMarker("debug"), getMessageToLog(), new RuntimeException(ID1, new InvalidTicketException(ID2))); + validateLogData(); + } + + @Test + public void verifyLogging61() { + logger.debug(getMessageToLog()); + validateLogData(); + } + + @Test + public void verifyLogging771() { + final TicketGrantingTicket t = mock(TicketGrantingTicket.class); + when(t.getId()).thenReturn(ID1); + when(t.toString()).thenReturn(ID1); + + logger.debug(getMessageToLogWithParams(), ID2, t); + validateLogData(); + } + + @Test + public void verifyLogging71() { + logger.debug(getMessageToLogWithParams(), ID2, ID1); + validateLogData(); + } + + @Test + public void verifyLogging81() { + logger.debug(getMessageToLogWithParams(), ID2, ID1, ID2); + validateLogData(); + } + + @Test + public void verifyLogging91() { + logger.debug(getMessageToLog(), new RuntimeException(ID1, new InvalidTicketException(ID2))); + validateLogData(); + } + + @Test + public void verifyLogging211() { + logger.info(getMarker("info"), getMessageToLog()); + validateLogData(); + } + + @Test + public void verifyLogging311() { + logger.info(getMarker("info"), getMessageToLogWithParams(), ID2, ID1); + validateLogData(); + } + + @Test + public void verifyLogging411() { + logger.info(getMarker("info"), getMessageToLogWithParams(), ID2, ID1, ID2); + validateLogData(); + } + + @Test + public void verifyLogging511() { + logger.info(getMarker("info"), getMessageToLog(), new RuntimeException(ID1, new InvalidTicketException(ID2))); + validateLogData(); + } + + @Test + public void verifyLogging611() { + logger.info(getMessageToLog()); + validateLogData(); + } + + @Test + public void verifyLogging711() { + logger.info(getMessageToLogWithParams(), ID2, ID1); + validateLogData(); + } + + @Test + public void verifyLogging811() { + logger.info(getMessageToLogWithParams(), ID2, ID1, ID2); + validateLogData(); + } + + @Test + public void verifyLogging911() { + logger.info(getMessageToLog(), new RuntimeException(ID1, new InvalidTicketException(ID2))); + validateLogData(); + } + + @Test + public void verifyLogging2111() { + logger.warn(getMarker("warn"), getMessageToLog()); + validateLogData(); + } + + @Test + public void verifyLogging3111() { + logger.warn(getMarker("warn"), getMessageToLogWithParams(), ID2, ID1); + validateLogData(); + } + + @Test + public void verifyLogging4111() { + logger.warn(getMarker("warn"), getMessageToLogWithParams(), ID2, ID1, ID2); + validateLogData(); + } + + @Test + public void verifyLogging5111() { + logger.warn(getMarker("warn"), getMessageToLog(), new RuntimeException(ID1, new InvalidTicketException(ID2))); + validateLogData(); + } + + @Test + public void verifyLogging6111() { + logger.warn(getMessageToLog()); + validateLogData(); + } + + @Test + public void verifyLogging7111() { + logger.warn(getMessageToLogWithParams(), ID2, ID1); + validateLogData(); + } + + @Test + public void verifyLogging8111() { + logger.warn(getMessageToLogWithParams(), ID2, ID1, ID2); + validateLogData(); + } + + @Test + public void verifyLogging9111() { + logger.warn(getMessageToLog(), new RuntimeException(ID1, new InvalidTicketException(ID2))); + validateLogData(); + } + + @Test + public void verifyLogging21110() { + logger.error(getMarker("error"), getMessageToLog()); + validateLogData(); + } + + @Test + public void verifyLogging31110() { + logger.error(getMarker("error"), getMessageToLogWithParams(), ID2, ID1); + validateLogData(); + } + + @Test + public void verifyLogging41110() { + logger.error(getMarker("error"), getMessageToLogWithParams(), ID2, ID1, ID2); + validateLogData(); + } + + @Test + public void verifyLogging51110() { + logger.error(getMarker("error"), getMessageToLog(), new RuntimeException(ID1, new InvalidTicketException(ID2))); + validateLogData(); + } + + @Test + public void verifyLogging61110() { + logger.error(getMessageToLog()); + validateLogData(); + } + + @Test + public void verifyLogging71110() { + logger.error(getMessageToLogWithParams(), ID2, ID1); + validateLogData(); + } + + @Test + public void verifyLogging81110() { + logger.error(getMessageToLogWithParams(), ID2, ID1, ID2); + validateLogData(); + } + + @Test + public void verifyLogging91110() { + logger.error(getMessageToLog(), new RuntimeException(ID1, new InvalidTicketException(ID2))); + validateLogData(); + } + + private String getMessageToLog() { + return String.format("Here is one %s and here is another %s", ID1, ID2); + } + + private String getMessageToLogWithParams() { + return "Here is one {} and here is another {}"; + } + + private void validateLogData() { + try { + assertTrue("Log file " + LOG_FILE.getCanonicalPath() + " does not exist", LOG_FILE.exists()); + + final String data = FileUtils.readFileToString(LOG_FILE); + assertTrue("Logged buffer data is blank in " + LOG_FILE.getCanonicalPath(), StringUtils.isNotBlank(data)); + assertFalse("Logged buffer data should not contain " + ID1, data.contains(ID1)); + assertFalse("Logged buffer data should not contain " + ID2, data.contains(ID2)); + assertFalse("Logged buffer data should not contain " + ID3, data.contains(ID3)); + } catch (final IOException e) { + fail(e.getMessage()); + } + } + + private Marker getMarker(final String name) { + final Marker marker = mock(Marker.class); + when(marker.getName()).thenReturn(name); + return marker; + } +} + diff --git a/cas-server-3.4.2/cas-server-core/src/test/resources/DSAPrivateKey01.key b/cas-server-core/src/test/resources/DSAPrivateKey01.key similarity index 100% rename from cas-server-3.4.2/cas-server-core/src/test/resources/DSAPrivateKey01.key rename to cas-server-core/src/test/resources/DSAPrivateKey01.key diff --git a/cas-server-3.4.2/cas-server-core/src/test/resources/DSAPublicKey01.key b/cas-server-core/src/test/resources/DSAPublicKey01.key similarity index 100% rename from cas-server-3.4.2/cas-server-core/src/test/resources/DSAPublicKey01.key rename to cas-server-core/src/test/resources/DSAPublicKey01.key diff --git a/cas-server-core/src/test/resources/WEB-INF/cas-servlet.xml b/cas-server-core/src/test/resources/WEB-INF/cas-servlet.xml new file mode 100644 index 000000000000..44f62e2f8ed0 --- /dev/null +++ b/cas-server-core/src/test/resources/WEB-INF/cas-servlet.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/cas-server-core/src/test/resources/cacert-org.cer b/cas-server-core/src/test/resources/cacert-org.cer new file mode 100644 index 000000000000..082eee78177d Binary files /dev/null and b/cas-server-core/src/test/resources/cacert-org.cer differ diff --git a/cas-server-core/src/test/resources/core-context.xml b/cas-server-core/src/test/resources/core-context.xml new file mode 100644 index 000000000000..f23b25addc34 --- /dev/null +++ b/cas-server-core/src/test/resources/core-context.xml @@ -0,0 +1,320 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + groupMembership + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-core/src/test/resources/essential-ssl.cer b/cas-server-core/src/test/resources/essential-ssl.cer new file mode 100644 index 000000000000..6e659d5da0e3 Binary files /dev/null and b/cas-server-core/src/test/resources/essential-ssl.cer differ diff --git a/cas-server-core/src/test/resources/keys/RSA1024Private.key b/cas-server-core/src/test/resources/keys/RSA1024Private.key new file mode 100644 index 000000000000..b31dd5153252 --- /dev/null +++ b/cas-server-core/src/test/resources/keys/RSA1024Private.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDxsrlRM4XYSEzFROcEg0m82ZfexI2HhEKXG5C0l8xVE5xIlrP/ +UsmMchF4V7uT830tvDu3ZslhH+y6mEJEm7nrlg1qdQXqygPbWDrvznxlFBgR/S9O +u+3JnkGCJ9uLrkSlkTO6HODun+JL1BvZpYWLqZ83bI5MZYdJ4xgesmcjEQIDAQAB +AoGAFEe1yv1yvpoabvcAF13YwxLS7ms5oORVHg6/DpgqGf1iQKj8g3Dz3mf31Uwv +PhPRLhQ8QoBKZ27hUyrRbKZQbC1ViXXtYino2Qxf08q+uY+IChMYmEzK/q6FLhtg +hg4QrBJijGRQ81kwK2DSFpGuF+TLa4+ldMfpLu73RU+de00CQQD53V9Chym+w7g+ +D3AKhyygkxnBRIDb5m/V+iZubGMIgIrgOOdiI0VWDD20LNL7QUgx5LTvj44dEI/G +oAyVXJovAkEA96IEbOGJibyrukZOcC7u4lYbs6tVMHXNEonmtVrLa06Yhh8NcLRF +46w5MAUHJDUDggCadd8dANGnb6bAXr0GvwJARhz+XBa9ehBFpPSEBhBET5K3iWoF +lq8k9rBJFHdJmtsnHSAanYk0LZ8luWdSlLqO3aFFvGtV/4XkMmI65bakdQJBANwf +GO/wS+Iz5DLg7Disf4ySHm3HjyJUlMY17u6mlsv8QXh3ger9VGLdZLhav85fkY6u +Gp9MhOuFceC9yaJtROECQQCO4bSKwUX6sE1K0LD5UmorCpnp3oQrfDSvZQr3Aq6W +OIPpdHKEyC9kvFx6HUYn6Y8tprZvVOueK7jOxnyYYW7/ +-----END RSA PRIVATE KEY----- diff --git a/cas-server-core/src/test/resources/keys/RSA1024Private.p8 b/cas-server-core/src/test/resources/keys/RSA1024Private.p8 new file mode 100644 index 000000000000..7ed95131daa2 Binary files /dev/null and b/cas-server-core/src/test/resources/keys/RSA1024Private.p8 differ diff --git a/cas-server-core/src/test/resources/keys/RSA1024Public.key b/cas-server-core/src/test/resources/keys/RSA1024Public.key new file mode 100644 index 000000000000..013c32894450 Binary files /dev/null and b/cas-server-core/src/test/resources/keys/RSA1024Public.key differ diff --git a/cas-server-core/src/test/resources/keys/RSA1024x509.pem b/cas-server-core/src/test/resources/keys/RSA1024x509.pem new file mode 100644 index 000000000000..9aefd166279b --- /dev/null +++ b/cas-server-core/src/test/resources/keys/RSA1024x509.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBDCCAm2gAwIBAgIJAOUmhgYijifpMA0GCSqGSIb3DQEBBQUAMGAxCzAJBgNV +BAYTAlVTMQswCQYDVQQIEwJBWjEQMA4GA1UEBxMHR2lsYmVydDEPMA0GA1UEChMG +QXBlcmVvMQwwCgYDVQQLEwNDQVMxEzARBgNVBAMTCmFwZXJlby5vcmcwHhcNMTUw +MjEzMTEyMTQ0WhcNNDIwNzAxMTEyMTQ0WjBgMQswCQYDVQQGEwJVUzELMAkGA1UE +CBMCQVoxEDAOBgNVBAcTB0dpbGJlcnQxDzANBgNVBAoTBkFwZXJlbzEMMAoGA1UE +CxMDQ0FTMRMwEQYDVQQDEwphcGVyZW8ub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDxsrlRM4XYSEzFROcEg0m82ZfexI2HhEKXG5C0l8xVE5xIlrP/UsmM +chF4V7uT830tvDu3ZslhH+y6mEJEm7nrlg1qdQXqygPbWDrvznxlFBgR/S9Ou+3J +nkGCJ9uLrkSlkTO6HODun+JL1BvZpYWLqZ83bI5MZYdJ4xgesmcjEQIDAQABo4HF +MIHCMB0GA1UdDgQWBBQcFWeT9G1tK8jiTLovaVMJ36W+xjCBkgYDVR0jBIGKMIGH +gBQcFWeT9G1tK8jiTLovaVMJ36W+xqFkpGIwYDELMAkGA1UEBhMCVVMxCzAJBgNV +BAgTAkFaMRAwDgYDVQQHEwdHaWxiZXJ0MQ8wDQYDVQQKEwZBcGVyZW8xDDAKBgNV +BAsTA0NBUzETMBEGA1UEAxMKYXBlcmVvLm9yZ4IJAOUmhgYijifpMAwGA1UdEwQF +MAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAND4+cmOEJmaF+O9IfnDb7UF+Pg0Om0p3 +tJJ6xnv4LyZzI9IdvNLuxoWsk7tOts6BBiYz/T86X0/oZ8NnwwAdljkCmqP3XlPF +CqrwhjQHetutxtz0SSkkRQvMXKZ650+ufNJcy0F0klVdxL1cWSX/y1Y0FpJ9atQK +hR89pmNbJ3E= +-----END CERTIFICATE----- diff --git a/cas-server-core/src/test/resources/log4j2.xml b/cas-server-core/src/test/resources/log4j2.xml new file mode 100644 index 000000000000..70280f5fd144 --- /dev/null +++ b/cas-server-core/src/test/resources/log4j2.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-core/src/test/resources/mfa-test-context.xml b/cas-server-core/src/test/resources/mfa-test-context.xml new file mode 100644 index 000000000000..4bd932b5be2f --- /dev/null +++ b/cas-server-core/src/test/resources/mfa-test-context.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + passwordHandler + oneTimePasswordHandler + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-core/src/test/resources/org/jasig/cas/authentication/handler/support/jaas.conf b/cas-server-core/src/test/resources/org/jasig/cas/authentication/handler/support/jaas.conf new file mode 100644 index 000000000000..3f48c2dba0e9 --- /dev/null +++ b/cas-server-core/src/test/resources/org/jasig/cas/authentication/handler/support/jaas.conf @@ -0,0 +1,23 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +CAS { +org.jasig.cas.authentication.handler.support.MockLoginModule required;}; + +TEST { +org.jasig.cas.authentication.handler.support.MockLoginModule required;}; diff --git a/cas-server-core/src/test/resources/services/sampleService.json b/cas-server-core/src/test/resources/services/sampleService.json new file mode 100644 index 000000000000..e784a34c92e9 --- /dev/null +++ b/cas-server-core/src/test/resources/services/sampleService.json @@ -0,0 +1,28 @@ +{ + "@class" : "org.jasig.cas.services.RegexRegisteredService", + "id" : 103935657744185, + "description" : null, + "serviceId" : "testId", + "name" : "testSaveAttributeReleasePolicyAllowedAttrRulesAndFilter", + "theme" : "testtheme", + "proxyPolicy" : { + "@class" : "org.jasig.cas.services.RegexMatchingRegisteredServiceProxyPolicy", + "pattern" : "https://.+" + }, + "enabled" : true, + "ssoEnabled" : false, + "evaluationOrder" : 1000, + "usernameAttributeProvider" : { + "@class" : "org.jasig.cas.services.DefaultRegisteredServiceUsernameProvider" + }, + "logoutType" : "BACK_CHANNEL", + "requiredHandlers" : [ "java.util.HashSet", [ "h1", "h2" ] ], + "attributeReleasePolicy" : { + "@class" : "org.jasig.cas.services.ReturnAllowedAttributeReleasePolicy", + "attributeFilter" : { + "@class" : "org.jasig.cas.services.support.RegisteredServiceRegexAttributeFilter", + "pattern" : "\\w+" + }, + "allowedAttributes" : [ "java.util.ArrayList", [ "1", "2", "3" ] ] + } +} diff --git a/cas-server-core/src/test/resources/truststore.jks b/cas-server-core/src/test/resources/truststore.jks new file mode 100644 index 000000000000..cfa9a7bce120 Binary files /dev/null and b/cas-server-core/src/test/resources/truststore.jks differ diff --git a/cas-server-core/src/xsd/cas2.xsd b/cas-server-core/src/xsd/cas2.xsd new file mode 100644 index 000000000000..fabd43325784 --- /dev/null +++ b/cas-server-core/src/xsd/cas2.xsd @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-core/src/xsd/cas2_spec.dtd b/cas-server-core/src/xsd/cas2_spec.dtd new file mode 100644 index 000000000000..dd4fde911eba --- /dev/null +++ b/cas-server-core/src/xsd/cas2_spec.dtd @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-documentation/images/cas_architecture.png b/cas-server-documentation/images/cas_architecture.png new file mode 100644 index 000000000000..8170a4876a4c Binary files /dev/null and b/cas-server-documentation/images/cas_architecture.png differ diff --git a/cas-server-documentation/images/cas_flow_diagram.png b/cas-server-documentation/images/cas_flow_diagram.png new file mode 100644 index 000000000000..274142bde8f2 Binary files /dev/null and b/cas-server-documentation/images/cas_flow_diagram.png differ diff --git a/cas-server-documentation/images/cas_proxy_flow_diagram.jpg b/cas-server-documentation/images/cas_proxy_flow_diagram.jpg new file mode 100644 index 000000000000..20739df5b1f0 Binary files /dev/null and b/cas-server-documentation/images/cas_proxy_flow_diagram.jpg differ diff --git a/cas-server-documentation/images/recommended_ha_architecture.png b/cas-server-documentation/images/recommended_ha_architecture.png new file mode 100644 index 000000000000..ab9b7a0aca4d Binary files /dev/null and b/cas-server-documentation/images/recommended_ha_architecture.png differ diff --git a/cas-server-documentation/index.md b/cas-server-documentation/index.md new file mode 100644 index 000000000000..22cf485a0a0e --- /dev/null +++ b/cas-server-documentation/index.md @@ -0,0 +1,41 @@ +--- +layout: default +title: CAS - Home +--- + +# Enterprise Single Sign-On + +* Java (Spring Webflow/MVC servlet) server component +* Pluggable authentication support (LDAP, database, X.509, 2-factor) +* Support for multiple protocols (CAS, SAML, OAuth, OpenID) +* Cross-platform client support (Java, .Net, PHP, Perl, Apache, etc) +* Integrates with uPortal, Liferay, BlueSocket, Moodle, and Google Apps to name a few + +CAS provides a friendly open source community that actively supports and contributes to the project. +While the project is rooted in higher-ed open source, it has grown to an international audience spanning +Fortune 500 companies and small special-purpose installations. + + +## Getting Started +We recommend reading the following documentation in order to plan and execute a CAS deployment. + +* [Architecture](planning/Architecture.html) +* [Installation Requirements](planning/Installation-Requirements.html) +* [Security Guide](planning/Security-Guide.html) +* [Authentication](installation/Configuring-Authentication-Components.html) +* [Maven Overlay Installation](installation/Maven-Overlay-Installation.html) +* [UI Customization](installation/User-Interface-Customization.html) +* [CAS Protocol](protocol/CAS-Protocol.html) +* [CAS Clients](integration/CAS-Clients.html) +* [Attribute Release](integration/Attribute-Release.html) + + +## Demo +The CAS web application is available for demo at [https://jasigcas.herokuapp.com](https://jasigcas.herokuapp.com) + +## Development +CAS development is powered by:
+ +
+ + diff --git a/cas-server-documentation/installation/Blacklist-Authentication.md b/cas-server-documentation/installation/Blacklist-Authentication.md new file mode 100644 index 000000000000..dda68842fe5c --- /dev/null +++ b/cas-server-documentation/installation/Blacklist-Authentication.md @@ -0,0 +1,34 @@ +--- +layout: default +title: CAS - Blacklist Authentication +--- + +# Blacklist Authentication +Blacklist authentication components are those that specifically deny access to a set of credentials. Those that fail to match against the predefined set will blindly be accepted. + +These are: + +* `RejectUsersAuthenticationHandler` + + +## Authentication Components +Support is enabled by including the following dependency in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-support-generic + ${cas.version} + +{% endhighlight %} + +###`AcceptUsersAuthenticationHandler` +{% highlight xml %} + + + + + + + +{% endhighlight %} diff --git a/cas-server-documentation/installation/Configuring-Authentication-Components.md b/cas-server-documentation/installation/Configuring-Authentication-Components.md new file mode 100644 index 000000000000..2241776f9604 --- /dev/null +++ b/cas-server-documentation/installation/Configuring-Authentication-Components.md @@ -0,0 +1,236 @@ +--- +layout: default +title: CAS - Configuring Authentication Components +--- + +# Authentication +The CAS authentication process is performed by several related components: + +######`PrincipalNameTransformer` +Transforms the user id string that is typed into the login form into a tentative Principal Name to be +validated by a specific type of Authentication Handler. + +######`AuthenticationManager` +Entry point into authentication subsystem. It accepts one or more credentials and delegates authentication to +configured `AuthenticationHandler` components. It collects the results of each attempt and determines effective +security policy. + +######`AuthenticationHandler` +Authenticates a single credential and reports one of three possible results: success, failure, not attempted. + +######`PrincipalResolver` +Converts information in the authentication credential into a security principal that commonly contains additional +metadata attributes (i.e. user details such as affiliations, group membership, email, display name). + +######`AuthenticationMetaDataPopulator` +Strategy component for setting arbitrary metadata about a successful authentication event; these are commonly used +to set protocol-specific data. + +Unless otherwise noted, the configuration for all authentication components is handled in `deployerConfigContext.xml`. + +## Authentication Manager +CAS ships with a single yet flexible authentication manager, `PolicyBasedAuthenticationManager`, that should be +sufficient for most needs. It performs authentication according to the following contract. + +For each given credential do the following: + +1. Iterate over all configured authentication handlers. +2. Attempt to authenticate a credential if a handler supports it. +3. On success attempt to resolve a principal. + 1. Check whether a resolver is configured for the handler that authenticated the credential. + 2. If a suitable resolver is found, attempt to resolve the principal. + 3. If a suitable resolver is not found, use the principal resolved by the authentication handler. +4. Check whether the security policy (e.g. any, all) is satisfied. + 1. If security policy is met return immediately. + 2. Continue if security policy is not met. +5. After all credentials have been attempted check security policy again and throw `AuthenticationException` +if not satisfied. + +There is an implicit security policy that requires at least one handler to successfully authenticate a credential, +but the behavior can be further controlled by setting `#setAuthenticationPolicy(AuthenticationPolicy)` +with one of the following policies. + + +######`AnyAuthenticationPolicy` +Satisfied if any handler succeeds. Supports a `tryAll` flag to avoid short circuiting at step 4.1 above and try every +handler even if one prior succeeded. This policy is the default and provides backward-compatible behavior with the +`AuthenticationManagerImpl` component of CAS 3.x. + + +######`AllAuthenticationPolicy` +Satisfied if and only if all given credentials are successfully authenticated. Support for multiple credentials is +new in CAS and this handler would only be acceptable in a multi-factor authentication situation. + + +######`RequiredHandlerAuthenticationPolicy` +Satisfied if an only if a specified handler successfully authenticates its credential. Supports a `tryAll` flag to +avoid short circuiting at step 4.1 above and try every handler even if one prior succeeded. This policy could be +used to support a multi-factor authentication situation, for example, where username/password authentication is +required but an additional OTP is optional. + +The following configuration snippet demonstrates how to configure `PolicyBasedAuthenticationManager` for a +straightforward multi-factor authentication case where username/password authentication is required and an additional OTP credential is optional; in both cases principals are resolved from LDAP. + +{% highlight xml %} + + + + + + + + + + + + + + + + + + + + + + + + + +{% endhighlight %} + +## Authentication Handlers +CAS ships with support for authenticating against many common kinds of authentication systems. +The following list provides a complete list of supported authentication technologies; jump to the section(s) of +interest. + +* [Database](Database-Authentication.html) +* [JAAS](JAAS-Authentication.html) +* [LDAP](LDAP-Authentication.html) +* [Legacy](Legacy-Authentication.html) +* [OAuth 1.0/2.0, OpenID](OAuth-OpenId-Authentication.html) +* [RADIUS](RADIUS-Authentication.html) +* [SPNEGO](SPNEGO-Authentication.html) (Windows) +* [Trusted](Trusted-Authentication.html) (REMOTE_USER) +* [X.509](X509-Authentication.html) (client SSL certificate) +* [Remote Address](Remote-Address-Authentication.html) + + +There are some additional handlers for small deployments and special cases: + +* [Whilelist](Whitelist-Authentication.html) +* [Blacklist](Blacklist-Authentication.html) + + +##Argument Extractors +Extractors are responsible to examine the http request received for parameters that describe the authentication request such as the requesting `service`, etc. Extractors exist for a number of supported authentication protocols and each create appropriate instances of `WebApplicationService` that contains the results of the extraction. + +Argument extractor configuration is defined at `src/main/webapp/WEB-INF/spring-configuration/argumentExtractorsConfiguration.xml`. Here's a brief sample: + +{% highlight xml %} + + + + + +{% endhighlight %} + + +###Components + +####`ArgumentExtractor` +Strategy parent interface that defines operations needed to extract arguments from the http request. + + +####`CasArgumentExtractor` +Argument extractor that maps the request based on the specifications of the CAS protocol. + + +####`GoogleAccountsArgumentExtractor` +Argument extractor to be used to enable Google Apps integration and SAML v2 specification. + + +####`SamlArgumentExtractor` +Argument extractor compliant with SAML v1.1 specification. + + +####`OpenIdArgumentExtractor` +Argument extractor compliant with OpenId protocol. + + +## Principal Resolution +Please [see this guide](Configuring-Principal-Resolution.html) more full details on principal resolution. + +### PrincipalNameTransformer Components +Authentication handlers that generally deal with username-password credentials +can be configured to transform the user id prior to executing the authentication sequence. The following components are available: + +######`NoOpPrincipalNameTransformer` +Default transformer, that actually does no transformation on the user id. + +######`PrefixSuffixPrincipalNameTransformer` +Transforms the user id by adding a postfix or suffix. + +######`ConvertCasePrincipalNameTransformer` +A transformer that converts the form uid to either lowercase or uppercase. The result is also trimmed. The transformer is also able +to accept and work on the result of a previous transformer that might have modified the uid, such that the two can be chained. + +#### Configuration +Here is an example configuration based for the `AcceptUsersAuthenticationHandler`: + +{% highlight xml %} + + + + + + + + + + +{% endhighlight %} + +## Authentication Metadata +`AuthenticationMetaDataPopulator` components provide a pluggable strategy for injecting arbitrary metadata into the +authentication subsystem for consumption by other subsystems or external components. Some notable uses of metadata +populators: + +* Supports the long term authentication feature +* SAML protocol support +* OAuth and OpenID protocol support. + +The default authentication metadata populators should be sufficient for most deployments. Where the components are +required to support optional CAS features, they will be explicitly identified and configuration will be provided. + +## Long Term Authentication +CAS has support for long term Ticket Granting Tickets, a feature that is also referred to as _"Remember Me"_ +to extends the length of the SSO session beyond the typical configuration. +Please [see this guide](Configuring-LongTerm-Authentication.html) for more details. + +## Proxy Authentication +Please [see this guide](Configuring-Proxy-Authentication.html) for more details. + +## Multi-factor Authentication (MFA) +Please [see this guide](Configuring-Multifactor-Authentication.html) for more details. + +## Login Throttling +CAS provides a facility for limiting failed login attempts to support password guessing and related abuse scenarios. +Please [see this guide](Configuring-Authentication-Throttling.html) for additional details on login throttling. + +## SSO Session Cookie +A ticket-granting cookie is an HTTP cookie set by CAS upon the establishment of a single sign-on session. +This cookie maintains login state for the client, and while it is valid, the client can present it to CAS in lieu of primary credentials. +Please [see this guide](Configuring-SSO-Session-Cookie.html) for additional details. diff --git a/cas-server-documentation/installation/Configuring-Authentication-Throttling.md b/cas-server-documentation/installation/Configuring-Authentication-Throttling.md new file mode 100644 index 000000000000..8cec2e801734 --- /dev/null +++ b/cas-server-documentation/installation/Configuring-Authentication-Throttling.md @@ -0,0 +1,135 @@ +--- +layout: default +title: CAS - Configuring Authentication Throttling +--- + +# Throttling Authentication Attempts +CAS provides a facility for limiting failed login attempts to support password guessing and related abuse scenarios. +A couple strategies are provided for tracking failed attempts: + +1. Source IP - Limit successive failed logins against any username from the same IP address. +2. Source IP and username - Limit succesive failed logins against a particular user from the same IP address. + +It would be straightforward to develop new components that implement alternative strategies. + +All login throttling components that ship with CAS limit successive failed login attempts that exceed a threshold +rate in failures per second. The following properties are provided to define the failure rate. + +* `failureRangeInSeconds` - Period of time in seconds during which the threshold applies. +* `failureThreshold` - Number of failed login attempts permitted in the above period. + +A failure rate of more than 1 per 3 seconds is indicative of an automated authentication attempt, which is a +reasonable basis for throttling policy. Regardless of policy care should be taken to weigh security against access; +overly restrictive policies may prevent legitimate authentication attempts. + + +## Throttling Components +The CAS login throttling components are listed below along with a sample configuration that implements a policy +preventing more than 1 failed login every 3 seconds. + + +#####`InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapter` +Uses a memory map to prevent successive failed login attempts from the same IP address. +{% highlight xml %} + +{% endhighlight %} + + +#####`InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter` +Uses a memory map to prevent successive failed login attempts for a particular username from the same IP address. +{% highlight xml %} + +{% endhighlight %} + + +#####`InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter` +Queries the data source used by the CAS audit facility to prevent successive failed login attempts for a particular +username from the same IP address. This component requires that the +[inspektr library](https://github.com/dima767/inspektr) used for CAS auditing be configured with +`JdbcAuditTrailManager`, which writes audit data to a database. +{% highlight xml %} + + + +{% endhighlight %} + +For additional instructions on how to configure auditing via Inspektr, +please [review the following guide](Logging.html). + +## High Availability Considerations for Throttling + +All of the throttling components are suitable for a CAS deployment that satisfies the +[recommended HA architecture](../planning/High-Availability-Guide.html). In particular deployments with multiple CAS +nodes behind a load balancer configured with session affinity can use either in-memory or _inspektr_ components. It is +instructive to discuss the rationale. Since load balancer session affinity is determined by source IP address, which +is the same criterion by which throttle policy is applied, an attacker from a fixed location should be bound to the +same CAS server node for successive authentication attempts. A distributed attack, on the other hand, where successive +request would be routed indeterminately, would cause haphazard tracking for in-memory CAS components since attempts +would be split across N systems. However, since the source varies, accurate accounting would be pointless since the +throttling components themselves assume a constant source IP for tracking purposes. The login throttling components +are simply not sufficient for detecting or preventing a distributed password brute force attack. + +For stateless CAS clusters where there is no session affinity, the in-memory components may afford some protection but +they cannot apply the rate strictly since requests to CAS hosts would be split across N systems. +The _inspektr_ components, on the other hand, fully support stateless clusters. + + +### Configuring Login Throttling +Login throttling configuration consists of two core components: + +1. A login throttle modeled as a Spring `HandlerInterceptorAdapter` component. +2. A scheduled task that periodically cleans up state to allow the throttle to relax. + +The period of scheduled task execution MUST be less than that defined by `failureRangeInSeconds` for proper throttle policy enforcement. For example, if `failureRangeInSeconds` is 3, then the quartz trigger that drives the task would be configured for less than 3000 (ms). + +It is convenient to place Spring configuration for login throttling components in `deployerConfigContext.xml`. +{% highlight xml %} + + + + + + +{% endhighlight %} + +Configure the throttle to fire during the login webflow by editing `cas-servlet.xml`: +{% highlight xml %} + + + + + + + + +{% endhighlight %} + + diff --git a/cas-server-documentation/installation/Configuring-LongTerm-Authentication.md b/cas-server-documentation/installation/Configuring-LongTerm-Authentication.md new file mode 100644 index 000000000000..c2b461d12aab --- /dev/null +++ b/cas-server-documentation/installation/Configuring-LongTerm-Authentication.md @@ -0,0 +1,137 @@ +--- +layout: default +title: CAS - Long Term Authentication +--- + +# Long Term Authentication +This feature, also known as *Remember Me*, extends the length of the SSO session beyond the typical period of hours +such that users can go days or weeks without having to log in to CAS. See the +[security guide](../planning/Security-Guide.html) +for discussion of security concerns related to long term authentication. + + +### Policy and Deployment Considerations +While users can elect to establish a long term authentication session, the duration is established through +configuration as a matter of security policy. Deployers must determine the length of long term authentication sessions +by weighing convenience against security risks. The length of the long term authentication session is configured +(somewhat unhelpfully) in seconds, but the Google calculator provides a convenient converter: + +[2 weeks in seconds](https://www.google.com/search?q=2+weeks+in+seconds&oq=2+weeks+in+seconds) + +The use of long term authentication sessions dramatically increases the length of time ticket-granting tickets are +stored in the ticket registry. Loss of a ticket-granting ticket corresponding to a long-term SSO session would require +the user to reauthenticate to CAS. A security policy that requires that long term authentication sessions MUST NOT +be terminated prior to their natural expiration would mandate a ticket registry component that provides for durable storage. Memcached is a notable example of a store that has no facility for durable storage. In many cases loss of +ticket-granting tickets is acceptable, even for long term authentication sessions. + +It's important to note that ticket-granting tickets and service tickets can be stored in separate registries, where +the former provides durable storage for persistent long-term authentication tickets and the latter provides less +durable storage for ephemeral service tickets. Thus deployers could mix `JpaTicketRegistry` and +`MemcachedTicketRegistry`, for example, to take advantage of their strengths, durability and speed respectively. + + +### Component Configuration +Long term authentication requires configuring CAS components in Spring configuration, modification of the CAS login +webflow, and UI customization of the login form. The length of the long term authentication session is represented +in following sections by the following property: + + # Long term authentication session length in seconds + rememberMeDuration=1209600 + +The duration of the long term authentication session is configured in two different places: +1. `ticketExpirationPolicies.xml` +2. `ticketGrantingTicketCookieGenerator.xml` + +Update the ticket-granting ticket expiration policy in `ticketExpirationPolicies.xml` to accommodate both long term +and stardard sessions. +{% highlight xml %} + + + + + + + +{% endhighlight %} + +Update the CASTGC cookie expiration in `ticketGrantingTicketCookieGenerator.xml` to match the long term authentication +duration: +{% highlight xml %} + +{% endhighlight %} + +Modify the `PolicyBasedAuthenticationManager` bean in `deployerConfigContext.xml` to include the +`RememberMeAuthenticationMetaDataPopulator` component that flags long-term SSO sessions: +{% highlight xml %} + + + + + + + + + + + + + +{% endhighlight %} + + +### Webflow Configuration +Two sections of `login-webflow.xml` require changes: +1. `credential` variable declaration +2. `viewLoginForm` action state + +Change the `credential` variable declaration as follows: +{% highlight xml %} + +{% endhighlight %} + +Change the `viewLoginForm` action state as follows: +{% highlight xml %} + + + + + + + + + + + + + +{% endhighlight %} + + +### User Interface Customization +A checkbox or other suitable control must be added to the CAS login form to allow user selection of long term +authentication. We recommend adding a checkbox control to `casLoginView.jsp` as in the following code snippet. +The only functional consideration is that the name of the form element is _rememberMe_. +{% highlight xml %} + + +{% endhighlight %} diff --git a/cas-server-documentation/installation/Configuring-Multifactor-Authentication.md b/cas-server-documentation/installation/Configuring-Multifactor-Authentication.md new file mode 100644 index 000000000000..a56b35656b7d --- /dev/null +++ b/cas-server-documentation/installation/Configuring-Multifactor-Authentication.md @@ -0,0 +1,93 @@ +--- +layout: default +title: CAS - Multifactor Authentication +--- + +# Multifactor Authentication (MFA) + +CAS provides a framework for multi-factor authentication (MFA). The design philosophy for MFA support follows from +the observation that institutional security policies with respect to MFA vary dramatically. We provide first class +API support for authenticating multiple credentials and a policy framework around authentication. The components +could be extended in a straightforward fashion to provide higher-level behaviors such as Webflow logic to assist, +for example, a credential upgrade scenario where a SSO session is started by a weaker credential but a particular +service demands reauthentication with a stronger credential. + +The authentication subsystem in CAS natively supports handling multiple credentials. While the default login form +and Webflow tier are designed for the simple case of accepting a single credential, all core API components that +interface with the authentication subsystem accept one or more credentials to authenticate. + +Beyond support for multiple credentials, an extensible policy framework is available to apply policy arbitrarily. +CAS ships with support for applying policy in the following areas: + +* Credential authentication success and failure. +* Service-specific authentication requirements. + +##`PolicyBasedAuthenticationManager` +CAS ships with an authentication manager component that is fundamentally MFA-aware. It supports a number of +policies, discussed above, that could facilitate a simple MFA design; for example, where multiple credentials are +invariably required to start a CAS SSO session. + +##`ContextualAuthenticationPolicy` +Strategy pattern component for applying security policy in an arbitrary context. These components are assumed to be +stateful once created. + +##`ContextualAuthenticationPolicyFactory` +Factory class for creating stateful instances of `ContextualAuthenticationPolicy` that apply to a particular context. + +##`AcceptAnyAuthenticationPolicyFactory` +Simple factory class that produces contextual security policies that always pass. This component is configured by +default in some cases to provide backward compatibility with CAS 3.x. + +##`RequiredHandlerAuthenticationPolicyFactory` +Factory that produces policy objects based on the security context of the service requesting a ticket. In particular the security +context is based on the required authentication handlers that must have successfully validated credentials in order to access +the service. A clarifying example is helpful; assume the following authentication components are defined in `deployerConfigContext.xml`: + +{% highlight xml %} + + + + + + + + + + + + + + +{% endhighlight %} + +Assume also the following beans are defined in `applicationContext.xml`: +{% highlight xml %} + + + +{% endhighlight %} + +With the above configuration in mind, the [service management facility](Service-Management.html) +may now be leveraged to register services that require specific kinds of credentials be used to access the service. +The kinds of required credentials are specified by naming the authentication handlers that accept them, for example, +`ldapHandler` and `oneTimePasswordHandler`. Thus a service could be registered that imposes security constraints like +the following: + +_Only permit users with SSO sessions created from both a username/password and OTP token to access this service._ diff --git a/cas-server-documentation/installation/Configuring-Principal-Resolution.md b/cas-server-documentation/installation/Configuring-Principal-Resolution.md new file mode 100644 index 000000000000..895a0d2055e1 --- /dev/null +++ b/cas-server-documentation/installation/Configuring-Principal-Resolution.md @@ -0,0 +1,109 @@ +--- +layout: default +title: CAS - Configuring Principal Resolution +--- + +# Configuring Principal Resolution +Principal resolution converts information in the authentication credential into a security principal that commonly contains additional +metadata attributes (i.e. user details such as affiliations, group membership, email, display name). + +A CAS principal contains a unique identifier by which the authenticated user will be known to all requesting +services. A principal also contains optional [attributes that may be released](../integration/Attribute-Release.html) +to services to support authorization and personalization. Principal resolution is a requisite part of the +authentication process that happens after credential authentication. + +CAS `AuthenticationHandler` components provide simple principal resolution machinery by default. For example, +the `LdapAuthenticationHandler` component supports fetching attributes and setting the principal ID attribute from +an LDAP query. In all cases principals are resolved from the same store as that which provides authentication. + +In many cases it is necessary to perform authentication by one means and resolve principals by another. +The `PrincipalResolver` component provides this functionality. A common use case for this this mix-and-match strategy +arises with X.509 authentication. It is common to store certificates in an LDAP directory and query the directory to +resolve the principal ID and attributes from directory attributes. The `X509CertificateAuthenticationHandler` may +be be combined with an LDAP-based principal resolver to accommodate this case. + +## Principal Resolution Components + +###`PersonDirectoryPrincipalResolver` +Uses the Jasig Person Directory library to provide a flexible principal resolution services against a number of data +sources. The key to configuring `PersonDirectoryPrincipalResolver` is the definition of an `IPersonAttributeDao` object. +The [Person Directory documentation](https://wiki.jasig.org/display/PDM15/Person+Directory+1.5+Manual) provides +configuration for two common examples: + +* [Database (JDBC)](https://wiki.jasig.org/x/bBjP) +* [LDAP](https://wiki.jasig.org/x/iBjP) + +We present a stub configuration here that can be modified accordingly by consulting the Person Directory documentation. + +{% highlight xml %} + + + + + + + + + + + +{% endhighlight %} + + +###`OpenIdPrincipalResolver` +Extension of `PersonDirectoryPrincipalResolver` that is specifically for use with OpenID credentials. The configuration +of this component is identical to that of `PersonDirectoryPrincipalResolver`. + + +###`SpnegoPrincipalResolver` +Extension of `PersonDirectoryPrincipalResolver` that is specifically for use with SPNEGO credentials. The configuration +is the same as that of `PersonDirectoryPrincipalResolver` but with an additional property, `transformPrincipalId`, +that provides a simple case transform on the principal ID. The following values are supported: + +* NONE +* LOWERCASE +* UPPERCASE + +{% highlight xml %} + +{% endhighlight %} + +###`X509SubjectPrincipalResolver` +Creates a principal ID from a format string composed of components from the subject distinguished name. +See the [X.509 principal resolver](#x_509) section for more information. + +###`X509SubjectDNPrincipalResolver` +Creates a principal ID from the certificate subject distinguished name. + +###`ChainingPrincipalResolver` +Delegates to one or more principal resolves in series to resolve a principal. The input to first configured resolver is the authenticated +credential; for every subsequent resolver, the input is a Credential whose ID is the resolved principal ID of the previous resolver. +A common use case for this component is resolving a temporary principal ID from an X.509 credential followed +by a search (e.g. LDAP, database) for the final principal based on the temporary ID. + +## PrincipalResolver vs. AuthenticationHandler +The principal resolution machinery provided by `AuthenticationHandler` components should be used in preference to +`PrincipalResolver` in any situation where the former provides adequate functionality. +If the principal that is resolved by the authentication handler +suffices, then a `null` value may be passed in place of the resolver bean id: + +{% highlight xml %} + + + + + + + +{% endhighlight %} diff --git a/cas-server-documentation/installation/Configuring-Proxy-Authentication.md b/cas-server-documentation/installation/Configuring-Proxy-Authentication.md new file mode 100644 index 000000000000..9bfa2a9ca631 --- /dev/null +++ b/cas-server-documentation/installation/Configuring-Proxy-Authentication.md @@ -0,0 +1,189 @@ +--- +layout: default +title: CAS - Proxy Authentication +--- + +# Proxy Authentication + +Proxy authentication support for CAS v1+ protocols is enabled by default, thus it is entirely a matter of CAS +client configuration to leverage proxy authentication features. + +
Service Configuration

+Note that each registered application in the registry must explicitly be configured +to allow for proxy authentication. See this guide +to learn about registering services in the registry. +

+ +Disabling proxy authentication components is recommended for deployments that wish to strategically avoid proxy +authentication as a matter of security policy. The simplest means of removing support is to remove support for the +`/proxy` and `/proxyValidate` endpoints on the CAS server. The relevant sections of `cas-servlet.xml` are listed +below and the aspects related to proxy authentication may either be commented out or removed altogether. + +{% highlight xml %} + + + + serviceValidateController + legacyValidateController + proxyController + proxyValidateController + passThroughController + healthCheckController + statisticsController + + + + + + + +{% endhighlight %} + + +## Proxy Handlers +Components responsible to determine what needs to be done to handle proxies. + + +###`CAS10ProxyHandler` +Proxy handler compliant with CAS v1 protocol that is designed to not handle proxy requests and simply return nothing as proxy support in the protocol is absent. + +{% highlight xml %} + +{% endhighlight %} + + +###`CAS20ProxyHandler` +Protocol handler compliant with CAS v2 protocol that is responsible to callback the URL provided and give it a pgtIou and a pgtId. + +{% highlight xml %} + +{% endhighlight %} + +##Handling SSL-enabled Proxy URLs +By default, CAS ships with a bundled HTTP client that is partly responsible to callback the URL +for proxy authentication. Note that this URL need also be authorized by the CAS service registry +before the callback can be made. [See this guide](Service-Management.md) for more info. + +If the callback URL is authorized by the service registry, and if the endpoint is under HTTPS +and protected by an SSL certificate, CAS will also attempt to verify the validity of the endpoint's +certificate before it can establish a successful connection. If the certificate is invalid, expired, +missing a step in its chain, self-signed or otherwise, CAS will fail to execute the callback. + +The HTTP client of CAS does present a local trust store that is similar to that of the Java platform. +It is recommended that this trust store be used to handle the management of all certificates that need +to be imported into the platform to allow CAS to execute the callback URL successfully. While by default, +the local trust store to CAS is empty, CAS will still utilize **both** the default and the local trust store. +The local trust store should only be used for CAS-related functionality of course, and the trust store file +can be carried over across CAS and Java upgrades, and certainly managed by the source control system that should +host all CAS configuration. + +The trust store configuration is inside the `applicationContext.xml` file, as such: + +{% highlight xml %} + +{% endhighlight %} + +##Returning PGT in Validation Response +In situations where using `CAS20ProxyHandler` may be undesirable, such that invoking a callback url to receive the proxy granting ticket is not feasible, +CAS may be configured to return the proxy-granting ticket id directly in the validation response. In order to successfully establish trust between the +CAS server and the application, private/public key pairs are generated by the client application and then **the public key** distributed and +configured inside CAS. CAS will use the public key to encrypt the proxy granting ticket id and will issue a new attribute `` +in the validation response, only if the service is authorized to receive it. + +Note that the return of the proxy granting ticket id is only carried out by the CAS validation response, provided the client +application issues a request to the `/p3/serviceValidate` endpoint (or `/p3/proxyValidate`). Other means of returning attributes to CAS, such as SAML1 +will **not** support the additional returning of the proxy granting ticket. + +###Configuration + +####Register the public key for service +Once you have received the public key from the client application owner, it must be first registered inside the CAS server's service registry: + +{% highlight xml %} +... + + + +... +{% endhighlight %} + +####Authorize PGT for Service +The service that holds the public key above must also be authorized to receive the proxy granting ticket id +as an attribute for the given attribute release policy of choice: + +{% highlight xml %} +... + + + +... +{% endhighlight %} + +####Decrypt the PGT id +Once the client application has received the `proxyGrantingTicket` id attribute in the CAS validation response, it can decrypt it +via its own private key. Since the attribute is base64 encoded by default, it needs to be decoded first before +decryption can occur. Here's a sample code snippet: + +{% highlight java %} + +final Map attributes = ... +final String encodedPgt = (String) attributes.get("proxyGrantingTicket"); +final PrivateKey privateKey = ... +final Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm()); +final byte[] cred64 = decodeBase64ToByteArray(encodedPgt); +cipher.init(Cipher.DECRYPT_MODE, privateKey); +final byte[] cipherData = cipher.doFinal(cred64); +return new String(cipherData); + +{% endhighlight %} + +###Components + +- `RegisteredServiceCipherExecutor` +Defines how to encrypt data based on registered service's public key, etc. + +- `DefaultRegisteredServiceCipherExecutor` +A default implementation of the `RegisteredServiceCipherExecutor` +that will use the service's public key to initialize the cipher to +encrypt and encode the value. All results are converted to base-64. + +- `CasAttributeEncoder` +Parent component that defines how a CAS attribute +is to be encoded and signed in the CAS validation response. + +- `DefaultCasAttributeEncoder` +The default implementation of the attribute encoder that will use a per-service key-pair +to encrypt. It will attempt to query the collection of attributes that resolved to determine +which attributes can be encoded. Attributes will be encoded via a `RegisteredServiceCipherExecutor`. + +{% highlight xml %} + + + + + +{% endhighlight %} diff --git a/cas-server-documentation/installation/Configuring-SSO-Session-Cookie.md b/cas-server-documentation/installation/Configuring-SSO-Session-Cookie.md new file mode 100644 index 000000000000..f00ad947fa48 --- /dev/null +++ b/cas-server-documentation/installation/Configuring-SSO-Session-Cookie.md @@ -0,0 +1,99 @@ +--- +layout: default +title: CAS - Configuring SSO Session Cookie +--- + +# SSO Session Cookie +A ticket-granting cookie is an HTTP cookie set by CAS upon the establishment of a single sign-on session. This cookie maintains login state for the client, and while it is valid, the client can present it to CAS in lieu of primary credentials. Services can opt out of single sign-on through the `renew` parameter. See the [CAS Protocol](../protocol/CAS-Protocol.html) for more info. + +## Configuration +The generation of the ticket-granting cookie is controlled by the file `cas-server-webapp\src\main\webapp\WEB-INF\spring-configuration\ticketGrantingTicketCookieGenerator.xml` + +{% highlight xml %} + + + + + + +{% endhighlight %} + +The cookie has the following properties: + +1. It is marked as secure. +2. Depending on container support, the cookie would be marked as http-only automatically. +3. The cookie value is encrypted and signed via secret keys that need to be generated upon deployment. + +## Cookie Value Encryption + +The cookie value is linked to the active ticket-granting ticket, the remote IP address that initiated the request +as well as the user agent that submitted the request. The final cookie value is then encrypted and signed +using `AES_128_CBC_HMAC_SHA_256` and `HMAC_SHA512` respectively. + +The secret keys are defined in the `cas.properties` file. While sample data is provided +for initial deployments, these keys **MUST** be regenerated per your specific environment. Each key +is a JSON Web Token with a defined length per the algorithm used for encryption and signing. +You may [use the following tool](https://github.com/mitreid-connect/json-web-key-generator) +to generate your own JSON Web Tokens. + +{% highlight properties %} +# CAS SSO Cookie Generation & Security +# See https://github.com/mitreid-connect/json-web-key-generator +# +# Do note that the following settings MUST be generated per deployment. +# +# Defaults at spring-configuration/ticketGrantingTicketCookieGenerator.xml +# The encryption secret key. By default, must be a octet string of size 256. +tgc.encryption.key=1PbwSbnHeinpkZOSZjuSJ8yYpUrInm5aaV18J2Ar4rM + +# The signing secret key. By default, must be a octet string of size 512. +tgc.signing.key=szxK-5_eJjs-aUj-64MpUZ-GPPzGLhYPLGl0wrYjYNVAGva2P0lLe6UGKGM7k8dWxsOVGutZWgvmY3l5oVPO3w + +{% endhighlight %} + +## Turning Off Cookie Value Encryption +if you wish to disable the signing and encryption of the cookie, in the +configuration xml file, use the following beans instead of those provided by default: + +{% highlight xml %} + + + + +{% endhighlight %} + +## Cookie Generation for Renewed Authentications + +By default, forced authentication requests that challenge the user for credentials +either via the [`renew` request parameter](../protocol/CAS-Protocol.html) +or via [the service-specific setting](Service-Management.html) of +the CAS service registry will always generate the ticket-granting cookie +nonetheless. What this means is, logging in to a non-SSO-participating application +via CAS nonetheless creates a valid CAS single sign-on session that will be honored on a +subsequent attempt to authenticate to a SSO-participating application. + +Plausibly, a CAS adopter may want this behavior to be different, such that logging in to a non-SSO-participating application +via CAS either does not create a CAS SSO session and the SSO session it creates is not honored for authenticating subsequently +to an SSO-participating application. This might better match user expectations. + +The controlling of this behavior is done via the `cas.properties` file: + +{% highlight properties %} +## +# Single Sign-On Session +# +# Indicates whether an SSO session should be created for renewed authentication requests. +# create.sso.renewed.authn=true +{% endhighlight %} + + diff --git a/cas-server-documentation/installation/Configuring-Ticket-Expiration-Policy.md b/cas-server-documentation/installation/Configuring-Ticket-Expiration-Policy.md new file mode 100644 index 000000000000..39e7d9746a3b --- /dev/null +++ b/cas-server-documentation/installation/Configuring-Ticket-Expiration-Policy.md @@ -0,0 +1,127 @@ +--- +layout: default +title: CAS - Configuring Ticket Expiration Policy Components +--- + + +## Ticket Expiration Policies +CAS supports a pluggable and extensible policy framework to control the expiration policy of ticket-granting tickets (TGT) and service tickets (ST). Both TGT and ST expiration policy beans are defined in the `/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/ticketExpirationPolicies.xml` file in the CAS distribution. + +
Policies Are Not Ticket-Specific

Ticket expiration policies are not specific to a particular kind of ticket, so it is possible to apply a policy intended for service tickets to ticket-granting tickets, although it may make little sense to do so.

+ + +### Ticket-Granting Ticket Policies +TGT expiration policy governs the time span during which an authenticated user may grant STs with a valid (non-expired) TGT without having to reauthenticate. An attempt to grant a ST with an expired TGT would require the user to reauthenticate to obtain a new (valid) TGT. + + +####`TimeoutExpirationPolicy` +The default expiration policy applied to TGTs provides for most-recently-used expiration policy, similar to a Web server session timeout. For example, a 2-hour time span with this policy in effect would require a TGT to be used every 2 hours or less, otherwise it would be marked as expired. + + +##### Parameters + +* `timeToKillInMilliSeconds` - Maximum amount of inactivity in ms from the last time the ticket was used beyond which it is considered expired. + + +##### Usage Example +{% highlight xml %} + + +{% endhighlight %} + + +####`HardTimeoutExpirationPolicy` +The hard timeout policy provides for finite ticket lifetime as measured from the time of creation. For example, a 4-hour time span for this policy means that a ticket created at 1PM may be used up until 5PM; subsequent attempts to use it will mark it expired and the user will be forced to reauthenticate. + + +##### Parameters +* `timeToKillInMilliSeconds` - Total ticket lifetime in ms. + + +##### Usage Example +{% highlight xml %} + + + c:timeToKillInMilliSeconds="14400000" /> +{% endhighlight %} + + + +####`ThrottledUseAndTimeoutExpirationPolicy` +The throttled timeout policy extends the TimeoutExpirationPolicy with the concept of throttling where a ticket may be used at most every N seconds. This policy was designed to thwart denial of service conditions where a rogue or misconfigured client attempts to consume CAS server resources by requesting high volumes of service tickets in a short time. + + +##### Parameters +* `timeToKillInMilliSeconds` - Maximum amount of inactivity in ms from the last time the ticket was used beyond which it is considered expired. +* `timeInBetweenUsesInMilliSeconds` - The minimum amount of time permitted between consecutive uses of a ticket. + + +##### Usage Example +{% highlight xml %} + + +{% endhighlight %} + + +####`NeverExpiresExpirationPolicy` +The never expires policy allows tickets to exist indefinitely. + +
Usage Warning!

Use of this policy has significant consequences to overall security policy and should be enabled only after thorough review by a qualified security team. There are also implications to server resource usage for the ticket registries backed by filesystem storage. Since disk storage for tickets can never be reclaimed for those registries with this policy in effect, use of this policy with those ticket registry implementations is strongly discouraged.

+ + +#####Usage Example +{% highlight xml %} + + +{% endhighlight %} + + +####`RememberMeDelegatingExpirationPolicy` +This policy implements applies to [long term authentication](Configuring-Authentication-Components.html) features of CAS known as "Remember Me". + + +#####Usage Example +{% highlight xml %} + + + + + + + + +{% endhighlight %} + + +### Service Ticket Policies + + +####`MultiTimeUseOrTimeoutExpirationPolicy` +This is the default policy applied to service tickets where a ticket is expired after a fixed number of uses or after a maximum period of inactivity elapses. + + +#####Parameters +* `numberOfUses` - Maximum number of times the ticket can be used. +* `timeToKill` - Maximum amount of inactivity from the last time the ticket was used beyond which it is considered expired. +* `timeUnit` - The unit of time based on which `timeToKill` will be calculated. + + +#####Usage Example +{% highlight xml %} + + + +{% endhighlight %} + + diff --git a/cas-server-documentation/installation/Configuring-Ticketing-Components.md b/cas-server-documentation/installation/Configuring-Ticketing-Components.md new file mode 100644 index 000000000000..bc9cb8a5ce6e --- /dev/null +++ b/cas-server-documentation/installation/Configuring-Ticketing-Components.md @@ -0,0 +1,147 @@ +--- +layout: default +title: CAS - Configuring Ticketing Components +--- + +# Configuring Ticketing Components +There are two core configurable ticketing components: + +* `TicketRegistry` - Provides for durable ticket storage. +* `ExpirationPolicy` - Provides a policy framework for ticket expiration semantics. + + +## Ticket Registry +The deployment environment and technology expertise generally determine the particular `TicketRegistry` component. +A cache-backed implementation is recommended for HA deployments, while the default `DefaultTicketRegistry` in-memory component may be suitable for small deployments. + + +### Default (In-Memory) Ticket Registry +`DefaultTicketRegistry` uses a `ConcurrentHashMap` for memory-backed ticket storage and retrieval. +This component does not preserve ticket state across restarts. There are a few configuration knobs available: + +* `initialCapacity` - `ConcurrentHashMap` initial capacity. +* `loadFactor` - `ConcurrentHashMap` load factor. +* `concurrencyLevel` - Allows tuning the `ConcurrentHashMap` for concurrent write support. + +All three arguments map to those of the [`ConcurrentHashMap` constructor](http://goo.gl/qKKg7). +{% highlight xml %} + +{% endhighlight %} + +### Cache-Based Ticket Registries +Cached-based ticket registries provide a high-performance solution for ticket storage in high availability +deployments. Components for the following caching technologies are provided: + +* [Hazelcast](Hazelcast-Ticket-Registry.html) +* [Ehcache](Ehcache-Ticket-Registry.html) +* [JBoss Cache](JBoss-Cache-Ticket-Registry.html) +* [Memcached](Memcached-Ticket-Registry.html) + + +### RDBMS Ticket Registries +RDBMS-based ticket registries provide a distributed ticket store across multiple CAS nodes. Components for the following caching technologies are provided: + +* [JPA](JPA-Ticket-Registry.html) + + +### Ticket Generators +CAS presents a pluggable architecture for generating unique ticket ids for each ticket type. The configuration of each generator is defined at `src\main\webapp\WEB-INF\spring-configuration\uniqueIdGenerators.xml`. Here's a brief sample: + +{% highlight xml %} + + + + + + + + + + + + + +{% endhighlight %} + +####Components + + +#####`UniqueTicketIdGenerator` +Strategy parent interface that describes operations needed to generate a unique id for a ticket. + +#####`DefaultUniqueTicketIdGenerator` +Uses numeric and random string generators to create a unique id, while supporting prefixes for each ticket type, as is outlined by the CAS protocol, as well as a suffix that typically is mapped to the CAS server node identifier in order to indicate which node is the author of this ticket. The latter configuration point helps with troubleshooting and diagnostics in a clustered CAS environment. + +#####`HostNameBasedUniqueTicketIdGenerator` +An extension of `DefaultUniqueTicketIdGenerator` that is able auto-configure the suffix based on the underlying host name. +In order to assist with multi-node deployments, in scenarios where CAS configuration +and specially `cas.properties` file is externalized, it would be ideal to simply just have one set +of configuration files for all nodes, such that there would for instance be one `cas.properties` file +for all nodes. This would remove the need to copy/sync configuration files over across nodes, again in a +situation where they are externalized. + +The drawback is that in keeping only one `cas.properties` file, we'd lose the ability +to define unique `host.name` property values for each node as the suffix, which would assist with troubleshooting +and diagnostics. To provide a remedy, this ticket generator is able to retrieve the `host.name` value directly from +the actual node name, rather than relying on the configuration, only if one isn't specified in +the `cas.properties` file. + +#####`SamlCompliantUniqueTicketIdGenerator` +Unique Ticket Id Generator compliant with the SAML 1.1 specification for artifacts, that is also compliant with the SAML v2 specification. + + +### Ticket Registry Cleaner +The ticket registry cleaner should be used for ticket registries that cannot manage their own state. That would include the default in-memory registry and the JPA ticket registry. Cache-based ticket registry implementations such as Memcached, Hazelcast or Ehcache do not require a registry cleaner. The ticket registry cleaner configuration is specified in the `spring-configuration/ticketRegistry.xml` file. + + +####Components + +#####`RegistryCleaner` +Strategy interface to denote the start of cleaning the registry. + + +#####`DefaultTicketRegistryCleaner` +The default ticket registry cleaner scans the entire CAS ticket registry for expired tickets and removes them. This process is only required so that the size of the ticket registry will not grow beyond a reasonable size. +The functionality of CAS is not dependent on a ticket being removed as soon as it is expired. Locking strategies may be used to support high availability environments. In a clustered CAS environment with several CAS nodes executing ticket cleanup, it is desirable to execute cleanup from only one CAS node at a time. + + +#####`LockingStrategy` +Strategy pattern for defining a locking strategy in support of exclusive execution of some process. + + +#####`NoOpLockingStrategy` +No-Op locking strategy that allows the use of `DefaultTicketRegistryCleaner` in environments where exclusive access to the registry for cleaning is either unnecessary or not possible. + +####Configuration +If you're using the default ticket registry configuration, your `/cas-server-webapp/WEB-INF/spring-configuration/ticketRegistry.xml` probably looks like this: + +{% highlight xml %} + + + + + + +{% endhighlight %} + + + +## Ticket Expiration Policies +CAS supports a pluggable and extensible policy framework to control the expiration policy of ticket-granting tickets (TGT) and service tickets (ST). [See this guide](Configuring-Ticket-Expiration-Policy.html) for details on how to configure the expiration policies. diff --git a/cas-server-documentation/installation/Database-Authentication.md b/cas-server-documentation/installation/Database-Authentication.md new file mode 100644 index 000000000000..a5869fe5a9ce --- /dev/null +++ b/cas-server-documentation/installation/Database-Authentication.md @@ -0,0 +1,191 @@ +--- +layout: default +title: CAS - Database Authentication +--- + +# Database Authentication +Database authentication components are enabled by including the following dependencies in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-support-jdbc + ${cas.version} + + + c3p0 + c3p0 + 0.9.1.2 + +{% endhighlight %} + +## Connection Pooling +All database authentication components require a `DataSource` for acquiring connections to the underlying database. +The use of connection pooling is _strongly_ recommended, and the [c3p0 library](http://www.mchange.com/projects/c3p0/) +is a good choice that we discuss here. +[Tomcat JDBC Pool](http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html) is another competent alternative. +Note that the connection pool dependency mentioned above should be modified according to the choice of connection pool +components. + + +### Pooled Data Source Example +A bean named `dataSource` must be defined for CAS components that use a database. A bean like the following should be +defined in `deployerConfigContext.xml`. +{% highlight xml %} + +{% endhighlight %} + +The following properties may be used as a starting point for connection pool configuration/tuning. + +{% highlight properties %} + # == Basic database connection pool configuration == + database.driverClass=org.postgresql.Driver + database.url=jdbc:postgresql://database.example.com/cas?ssl=true + database.user=somebody + database.password=meaningless + database.pool.minSize=6 + database.pool.maxSize=18 + + # Maximum amount of time to wait in ms for a connection to become + # available when the pool is exhausted + database.pool.maxWait=10000 + + # Amount of time in seconds after which idle connections + # in excess of minimum size are pruned. + database.pool.maxIdleTime=120 + + # Number of connections to obtain on pool exhaustion condition. + # The maximum pool size is always respected when acquiring + # new connections. + database.pool.acquireIncrement=6 + + # == Connection testing settings == + + # Period in s at which a health query will be issued on idle + # connections to determine connection liveliness. + database.pool.idleConnectionTestPeriod=30 + + # Query executed periodically to test health + database.pool.connectionHealthQuery=select 1 + + # == Database recovery settings == + + # Number of times to retry acquiring a _new_ connection + # when an error is encountered during acquisition. + database.pool.acquireRetryAttempts=5 + + # Amount of time in ms to wait between successive aquire retry attempts. + database.pool.acquireRetryDelay=2000 +{% endhighlight %} + + +## Database Components +CAS provides the followng components to accommodate different database authentication needs. + +######`QueryDatabaseAuthenticationHandler` +Authenticates a user by comparing the (hashed) user password against the password on record determined by a +configurable database query. `QueryDatabaseAuthenticationHandler` is by far the most flexible and easiest to +configure for anyone proficient with SQL, but `SearchModeSearchDatabaseAuthenticationHandler` provides an alternative +for simple queries based solely on username and password and builds the SQL query using straightforward inputs. + +The following database schema for user data is assumed in the following two examples that leverage SQL queries +to authenticate users. + +{% highlight sql %} + create table users ( + username varchar(50) not null, + password varchar(50) not null, + active bit not null ); +{% endhighlight %} + +The following example uses an MD5 hash algorithm and searches exclusively for _active_ users. +{% highlight xml %} + + + +{% endhighlight %} + + +######`SearchModeSearchDatabaseAuthenticationHandler` +Searches for a user record by querying against a username and (hashed) password; the user is authenticated if at +least one result is found. + +The following example uses a SHA1 hash algorithm to authenticate users. +{% highlight xml %} + + + +{% endhighlight %} + + +######`BindModeSearchDatabaseAuthenticationHandler` +Authenticates a user by attempting to create a database connection using the username and (hashed) password. + +The following example does not perform any password encoding since most JDBC drivers natively encode plaintext +passwords to the appropriate format required by the underlying database. Note authentication is equivalent to the +ability to establish a connection with username/password credentials. This handler is the easiest to configure +(usually none required), but least flexible, of the database authentication components. +{% highlight xml %} + +{% endhighlight %} + + +######`QueryAndEncodeDatabaseAuthenticationHandler` +A JDBC querying handler that will pull back the password and +the private salt value for a user and validate the encoded +password using the public salt value. Assumes everything +is inside the same database table. Supports settings for +number of iterations as well as private salt. + +This password encoding method, combines the private Salt and the public salt which it prepends to the password before hashing. +If multiple iterations are used, the bytecode Hash of the first iteration is rehashed without the salt values. +The final hash is converted to Hex before comparing it to the database value. + +{% highlight xml %} + + + + +{% endhighlight %} diff --git a/cas-server-documentation/installation/Ehcache-Ticket-Registry.md b/cas-server-documentation/installation/Ehcache-Ticket-Registry.md new file mode 100644 index 000000000000..1b0dbf0c89ad --- /dev/null +++ b/cas-server-documentation/installation/Ehcache-Ticket-Registry.md @@ -0,0 +1,275 @@ +--- +layout: default +title: CAS - Ehcache Ticket Registry +--- + +# Ehcache Ticket Registry +Ehcache integration is enabled by including the following dependency in the Maven WAR overlay: + + + org.jasig.cas + cas-server-integration-ehcache + ${cas.version} + + +`EhCacheTicketRegistry` stores tickets in an [Ehcache](http://ehcache.org/) instance. + +We present two configurations: + +1. Single instance memory-backed cache with disk overflow for simple cases. +2. Distributed cache with peer replication over RMI for HA deployments. + + +## Memory Cache with Disk Overflow +The following Spring configuration provides a template for `ticketRegistry.xml`. +{% highlight xml %} + + + + + + + + + +{% endhighlight %} + +The Ehcache configuration file `ehcache-failsafe.xml` mentioned in the Spring configuration above: +{% highlight xml %} + + + + + +{% endhighlight %} + + +## Distributed Cache +Distributed caches are recommended for HA architectures since they offer fault tolerance in the ticket storage subsystem. The registry maintains service tickets and ticket-granting tickets in two separate caches, so that: + +* Ticket Granting Tickets remain valid for a long time, replicated asynchronously +* Service Tickets are short lived and must be replicated right away because the requests to validate them may very likely arrive at different CAS cluster nodes + + +###RMI Replication +Ehcache supports [RMI](http://docs.oracle.com/javase/6/docs/technotes/guides/rmi/index.html) replication for distributed caches composed of two or more nodes. To learn more about RMI replication with Ehcache, [see this resource](http://ehcache.org/documentation/user-guide/rmi-replicated-caching). + + +####Spring configuration template for `ticketRegistry.xml`. +{% highlight xml %} + + + + + + + + + + + + + + + + +{% endhighlight %} + +The Ehcache configuration for `ehcache-replicated.xml` mentioned in the Spring config follows. +{% highlight xml %} + + + + + + + + + + + +{% endhighlight %} + +Use either manual or automatic peer discovery to assemble members of distributed cache. If manual discover is used +for configuration, the file would vary according to the node on which CAS is deployed. For that reason it may be +helpful to place the configuration file on the filesystem at a well-known location and reference it in the +`EhCacheManagerFactoryBean` above as follows: +{% highlight xml %} + +{% endhighlight %} + + + +###JGroups Replication +[JGroups](http://ehcache.org/documentation/2.5/replication/jgroups-replicated-caching) can be used as the underlying mechanism for the replication operations in Ehcache. JGroups offers a very flexible protocol stack, reliable unicast, and multicast message transmission. On the down side JGroups can be complex to configure and some protocol stacks have dependencies on others. + + +####Spring configuration template for `ticketRegistry.xml`. +The configuration is similar to above, except that ticket replicators and cache loaders need to be swapped out for their JGroups counterpart: + +{% highlight xml %} +... + + + + + + + + + + + + + + + +... +{% endhighlight %} + +The Ehcache JGroups confguration itself needs to be altered to be similar to the following: + +{% highlight xml %} + + + + + + + + +{% endhighlight %} + +Your maven overlay `pom.xml` will also need to declare the following dependencies: +{% highlight xml %} + + org.jgroups + jgroups + ${jgroups.version} + + + + net.sf.ehcache + ehcache-jgroupsreplication + ${ehcache-jgroups.version} + +{% endhighlight %} + + + +###Eviction Policy +Ehcache manages the internal eviction policy of cached objects via `timeToIdle` and `timeToLive` settings. This results of having *no need* for a Ticket Registry Cleaner. + +There have been reports of cache eviction problems when tickets are expired, but haven't been removed from the cache due to ehache configuration. This can be a problem because old ticket references "hang around" in the cache despite being expired. In other words, Ehcache does [inline eviction](http://lists.terracotta.org/pipermail/ehcache-list/2011-November/000423.html) where expired objects in the cache object won't be collected from memory until the cache max size is reached or the expired entry is explicitly accessed. To reclaim memory on expired tickets, cache eviction policies are must be carefully configured to avoid memory creep. Disk offload and/or a more aggressive eviction could provide a suitable workaround. + + +###Troubleshooting Guidelines + +* You will need to ensure that network communication across CAS nodes is allowed and no firewall or other component is blocking traffic. + +* If you are running this on a server with active firewalls, you will probably need to specify a fixed `remoteObjectPort`, within the `cacheManagerPeerListenerFactory`. +* Depending on environment settings and version of Ehcache used, you may also have to adjust the `shared` setting above. +* Ensure that each cache manager specified a name that matches the Ehcache configuration itself. +* You may also need to adjust your expiration policy to allow for a larger time span, specially for service tickets depending on network traffic and communication delay across CAS nodes particualrly in the event that a node is trying to join the cluster. diff --git a/cas-server-documentation/installation/Hazelcast-Ticket-Registry.md b/cas-server-documentation/installation/Hazelcast-Ticket-Registry.md new file mode 100644 index 000000000000..c6d5b0538d8a --- /dev/null +++ b/cas-server-documentation/installation/Hazelcast-Ticket-Registry.md @@ -0,0 +1,48 @@ +--- +layout: default +title: CAS - Hazelcast Ticket Registry +--- + +# Hazelcast Ticket Registry + +Hazelcast Ticket Registry is a distributed ticket registry implementation based on [Hazelcast distributed grid library](http://hazelcast.org/). The registry implementation is cluster-aware and is able to auto-join a cluster of all the CAS nodes that expose this registry. Hazelcast will use port auto-increment feature to assign a TCP port to each member of a cluster starting from initially provided arbitrary port (`5701` by default). + +Hazelcast will evenly distribute the ticket data among all the members of a cluster in a very efficient manner. Also, by default, the data collection on each node is configured with 1 backup copy, so that Hazelcast will use it to make strong data consistency guarantees i.e. the loss of data on live nodes will not occur should any other *primary data owner* members die. The data will be re-partitioned among the remaining live cluster members. + +This ticket registry implementation is enabled by simply including the module in the Maven overlay pom: + +{% highlight xml %} + + org.jasig.cas + cas-server-integration-hazelcast + ${cas.version} + +{% endhighlight %} + +## Configuration + +This implementation auto-configures most of the internal details of the underlying Hazelcast instance and the distributed `IMap` for tickets storage. +The only required configuration value on each CAS node in the cluster is a comma-separated list of *ALL* the member nodes defined in the configuration +property `hz.cluster.members` (in `cas.properties` file). For example: `hz.cluster.members=cas1.example.com,cas2.example.com` + +Other optional properties that could be set are: + +* `hz.cluster.port` (default value is `5701`) +* `hz.cluster.portAutoIncrement` (default value is `true`) +* TGT time to live value for this implementation is set via `tgt.maxTimeToLiveInSeconds` and defaults to `28800` +* ST time to live value for this implementation is set via `st.timeToKillInSeconds` and defaults to `10` + +## Logging +To enable additional logging for the registry, configure the log4j configuration file to add the following +levels: + +{% highlight xml %} +... + + + + +... +{% endhighlight %} + + diff --git a/cas-server-documentation/installation/Installing-ServicesMgmt-Webapp.md b/cas-server-documentation/installation/Installing-ServicesMgmt-Webapp.md new file mode 100644 index 000000000000..19780ad2c061 --- /dev/null +++ b/cas-server-documentation/installation/Installing-ServicesMgmt-Webapp.md @@ -0,0 +1,135 @@ +--- +layout: default +title: CAS - Services Management Webapp +--- +# Services Management Webapp + +The services management webapp is no more part of the CAS server and is a standalone web application: `cas-management-webapp`. + +Nonetheless, one must keep in mind that both applications (the CAS server and the services management webapp) share the _same_ configuration for the CAS services: + +* the management webapp is used to add/edit/delete all the CAS services +* the CAS server loads/relies on all these defined CAS services to process all incoming requests. + +You can install the services management webapp in your favourite applications server, there is no restriction. +Though, you need at first to configure it according to your environment. Towards that goal, the best way to proceed is to create your own services management webapp using a [Maven overlay](http://maven.apache.org/plugins/maven-war-plugin/overlays.html) based on the CAS services management webapp: + +{% highlight xml %} + + org.jasig.cas + cas-management-webapp + ${cas.version} + war + runtime + +{% endhighlight %} + + +## Authentication method + +By default, the `cas-management-webapp` is configured to authenticate against a CAS server. We assume that it's the case in this documentation. However, you could change the authentication method by overriding the `WEB-INF/spring-configuration/securityContext.xml` file. + + +##Securing Access and Authorization +Access to the management webapp is controlled via Spring Security. Rules are defined in the `/cas-management-webapp/src/main/webapp/WEB-INF/managementConfigContext.xml` file. + + +###Static List of Users +By default, access is limited to a static list of users whose credentials may be specified in a `user-details.properties` file that should be available on the runtime classpath. + +{% highlight xml %} + +{% endhighlight %} + +You can change the location of this file, by uncommenting the following key in your `cas-management.properties` file: + +{% highlight bash %} +## +# User details file location that contains list of users +# who are allowed access to the management webapp: +# +# user.details.file.location = classpath:user-details.properties +{% endhighlight %} + +The format of the file should be as such: + +{% highlight bash %} +# The syntax of each entry should be in the form of: +# +# username=password,grantedAuthority[,grantedAuthority][,enabled|disabled] + +# Example: +# casuser=notused,ROLE_ADMIN +{% endhighlight %} + + +###LDAP-managed List of Users +If you wish allow access to the services management application via an LDAP group/server, open up the `deployerConfigContext` file of the management web application and adjust for the following: + +{% highlight xml %} + + +{% endhighlight %} + +You will also need to ensure that the `spring-security-ldap` dependency is available to your build at runtime: + +{% highlight xml %} + + org.springframework.security + spring-security-ldap + ${spring.security.ldap.version} + + + org.springframework + spring-aop + + + org.springframework + spring-tx + + + org.springframework + spring-beans + + + org.springframework + spring-context + + + org.springframework + spring-core + + + +{% endhighlight %} + + +## Urls Configuration + +The urls configuration of the CAS server and management applications can be done by overriding the default `WEB-INF/cas-management.properties` file: + + # CAS + cas.host=http://localhost:8080 + cas.prefix=${cas.host}/cas + cas.securityContext.casProcessingFilterEntryPoint.loginUrl=${cas.prefix}/login + cas.securityContext.ticketValidator.casServerUrlPrefix=${cas.prefix} + # Management + cas-management.host=${cas.host} + cas-management.prefix=${cas-management.host}/cas-management + cas-management.securityContext.serviceProperties.service=${cas-management.prefix}/j_spring_cas_security_check + cas-management.securityContext.serviceProperties.adminRoles=ROLE_ADMIN + +When authenticating against a CAS server, the services management webapp will be processed as a regular CAS service and thus, needs to be defined in the services registry (of the CAS server). + + +## Services Registry + +You also need to define the *common* services registry by overriding the `WEB-INF/managementConfigContext.xml` file and set the appropriate `serviceRegistryDao` (see above: *Persisting Registered Service Data*). It should be the same configuration you already use in your CAS server (in the `WEB-INF/deployerConfigContext.xml` file). + diff --git a/cas-server-documentation/installation/JAAS-Authentication.md b/cas-server-documentation/installation/JAAS-Authentication.md new file mode 100644 index 000000000000..0b21888c4396 --- /dev/null +++ b/cas-server-documentation/installation/JAAS-Authentication.md @@ -0,0 +1,56 @@ +--- +layout: default +title: CAS - JAAS Authentication +--- + +# JAAS Authentication +[JAAS](http://docs.oracle.com/javase/6/docs/technotes/guides/security/jaas/JAASRefGuide.html) is a Java standard +authentication and authorization API. JAAS is configured via externalized plain text configuration file. +Using JAAS with CAS allows modification of the authentication process without having to rebuild and redeploy CAS +and allows for PAM-style multi-module "stacked" authentication. + + +## JAAS Components +JAAS components are provided in the CAS core module and require no additional dependencies to use. + + +######`JaasAuthenticationHandler` +The JAAS handler delegates to the built-in JAAS subsystem to perform authentication according to the +directives in the JAAS config file. The handler only exposes a single configuration property: + +* `realm` - JAAS realm name. (Defaults to _CAS_.) + +The following configuration excerpt demonstrates how to configure the JAAS handler in `deployerConfigContext.xml`: + +{% highlight xml %} + +{% endhighlight %} + + +## JAAS Configuration File +The default JAAS configuration file is located at `$JRE_HOME/lib/security/java.security`. It's important to note +that JAAS configuration applies to the entire JVM. The path to the JAAS configuration file in effect may be altered +by setting the `java.security.auth.login.config` system property to an alternate file path. +A sample JAAS configuration file is provided for reference. + + /** + * Login Configuration for JAAS. First try Kerberos, then LDAP, then AD + * Note that a valid krb5.conf must be supplied to the JVM for Kerberos auth + * -Djava.security.krb5.conf=/etc/krb5.conf + */ + CAS { + com.ibm.security.auth.module.Krb5LoginModule sufficient + debug=FALSE; + edu.uconn.netid.jaas.LDAPLoginModule sufficient + java.naming.provider.url="ldap://ldap.my.org:389/dc=my,dc=org" + java.naming.security.principal="uid=cas,dc=my,dc=org" + java.naming.security.credentials="password" + Attribute="uid" + startTLS="true"; + edu.uconn.netid.jaas.LDAPLoginModule sufficient + java.naming.provider.url="ldaps://ad.my.org:636/dc=ad,dc=my,dc=org" + java.naming.security.principal="cas@ad.my.org" + java.naming.security.credentials="password" + Attribute="sAMAccountName"; + }; diff --git a/cas-server-documentation/installation/JBoss-Cache-Ticket-Registry.md b/cas-server-documentation/installation/JBoss-Cache-Ticket-Registry.md new file mode 100644 index 000000000000..2e1a9aae4571 --- /dev/null +++ b/cas-server-documentation/installation/JBoss-Cache-Ticket-Registry.md @@ -0,0 +1,126 @@ +--- +layout: default +title: CAS - JBoss Cache Ticket Registry +--- + +# JBoss Cache Ticket Registry + +
Deprecated Module!

The JBoss Cache Ticket Registry module is deprecated and will no longer be maintained. Furthermore, the module may be removed in subsequent CAS releases. Please try to use other options provided by the CAS server for your distributed ticket registry needs.

+ +JBoss integration is enabled by including the following dependency in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-integration-jboss + ${cas.version} + +{% endhighlight %} + +`JBossCacheTicketRegistry` stores data in a [JBoss Cache](http://www.jboss.org/jbosscache/) that supports distributed +caches for failover support and is therefore suitable for HA deployments. Almost all of the configuration for this +component happens in the JBoss Cache configuration file; we present a starting configuration here but readers should +consult JBoss Cache +[configuration documentation](http://docs.jboss.org/jbosscache/3.2.1.GA/userguide_en/html/configuration.html) +for further details. + +Sample `JBossCacheTicketRegistry` configuration for `ticketRegistry.xml`. +{% highlight xml %} + + + +{% endhighlight %} + +The `jbossCache.xml` file may be bundled in the overlay which allows it to be accessed from the classpath, as in the +example above, or it may be located on the filesystem. A sample `jbossCache.xml` file follows. +{% highlight xml %} + + + + + jboss:service=Naming + jboss:service=TransactionManager + + + org.jboss.cache.transaction.DummyTransactionManagerLookup + + + + REPEATABLE_READ + + + REPL_SYNC + + + false + + + 0 + + + 0 + + + TreeCache-Cluster + + + + + + + + + + + + + + + + + + + + + true + + + 15000 + + + 15000 + + + 10000 + + + + + false + 130 + + +{% endhighlight %} diff --git a/cas-server-documentation/installation/JPA-Ticket-Registry.md b/cas-server-documentation/installation/JPA-Ticket-Registry.md new file mode 100644 index 000000000000..38049194cd69 --- /dev/null +++ b/cas-server-documentation/installation/JPA-Ticket-Registry.md @@ -0,0 +1,304 @@ +--- +layout: default +title: CAS - JPA Ticket Registry +--- + + +# JPA Ticket Registry +The JPA Ticket Registry allows CAS to store client authenticated state data (tickets) in a database back-end such as MySQL. + +
Usage Warning!

Using a RDBMS as the back-end persistence choice for Ticket Registry state management is a fairly unnecessary and complicated process. Ticket registries generally do not need the durability that comes with RDBMS and unless you are already outfitted with clustered RDBMS technology and the resources to manage it, the complexity is likely not worth the trouble. Given the proliferation of hardware virtualization and the redundancy and vertical scaling they often provide, more suitable recommendation would be the default in-memory ticket registry for a single node CAS deployment and distributed cache-based registries for higher availability.

+ + +# Configuration + +- Adjust the `src/main/webapp/WEB-INF/spring-configuration/ticketRegistry.xml` with the following: + +{% highlight xml %} + + + + + + org.jasig.cas.services + org.jasig.cas.ticket + org.jasig.cas.adaptors.jdbc + + + + + + + + ${database.dialect} + create-drop + ${database.batchSize} + + + + + + + + + + + + + + + + +{% endhighlight %} + +The above snippet assumes that data source information and connection details are defined. + +- Configure other JPA dependencies: + +In the `pom.xml` file of the Maven overlay, adjust for the following dependencies: + +{% highlight xml %} +... + + org.hibernate + hibernate-core + ${hibernate.core.version} + runtime + + + + org.hibernate + hibernate-entitymanager + ${hibernate.core.version} + runtime + +... +{% endhighlight %} + +##Cleaner Locking Strategy +The above shows a JPA 2.0 implementation of an exclusive, non-reentrant lock, `JpaLockingStrategy`, to be used with the JPA-backed ticket registry. + +This will configure the cleaner with the following defaults: + +* tableName = "LOCKS" +* uniqueIdColumnName = "UNIQUE_ID" +* applicationIdColumnName = "APPLICATION_ID" +* expirationDataColumnName = "EXPIRATION_DATE" +* platform = SQL92 +* lockTimeout = 3600 (1 hour) + + +# Database Configuration + +## JDBC Driver +CAS must have access to the appropriate JDBC driver for the database. Once you have obtained the appropriate driver and configured the data source, place the JAR inside the lib directory of your web server environment (i.e. `$TOMCAT_HOME/lib`) + + +## Schema +If the user has sufficient privileges on start up, the database tables should be created. The database user MUST have `CREATE/ALTER` privileges to take advantage of automatic schema generation and schema updates. + + +## Deadlocks +The Hibernate SchemaExport DDL creation tool *may* fail to create two very import indices when generating the ticket tables. The absence of these indices dramatically increases the potential for database deadlocks under load. +If the indices were not created you should manually create them before placing your CAS configuration into a production environment. + +To review indices, you may use the following MYSQL-based sample code below: + +{% highlight sql %} +show index from SERVICETICKET where column_name='ticketGrantingTicket_ID'; +show index from TICKETGRANTINGTICKET where column_name='ticketGrantingTicket_ID'; +{% endhighlight %} + +To create indices that are missing, you may use the following sample code below: + + +### MYSQL +{% highlight sql %} +CREATE INDEX ST_TGT_FK_I ON SERVICETICKET (ticketGrantingTicket_ID); +CREATE INDEX ST_TGT_FK_I ON TICKETGRANTINGTICKET (ticketGrantingTicket_ID); +{% endhighlight %} + + +###ORACLE +{% highlight sql %} +CREATE INDEX "ST_TGT_FK_I" + ON SERVICETICKET ("TICKETGRANTINGTICKET_ID") + COMPUTE STATISTICS; + +/** Create index on TGT self-referential foreign-key constraint */ +CREATE INDEX "TGT_TGT_FK_I" + ON TICKETGRANTINGTICKET ("TICKETGRANTINGTICKET_ID") + COMPUTE STATISTICS; +{% endhighlight %} + + +## Ticket Cleanup + +The use `JpaLockingStrategy` is strongly recommended for HA environments where multiple nodes are attempting ticket cleanup on a shared database. `JpaLockingStrategy` can auto-generate the schema for the target platform. A representative schema is provided below that applies to PostgreSQL: + + +{% highlight sql %} +CREATE TABLE locks ( + application_id VARCHAR(50) NOT NULL, + unique_id VARCHAR(50) NULL, + expiration_date TIMESTAMP NULL +); + +ALTER TABLE locks ADD CONSTRAINT pk_locks + PRIMARY KEY (application_id); +{% endhighlight %} + +
Platform-Specific Issues

The exact DDL to create the LOCKS table may differ from the above. For example, on Oracle platforms the `expiration_date` column must be of type `DAT`E. Use the `JpaLockingStrategy` which can create and update the schema automatically to avoid platform-specific schema issues.

+ + +## Connection Pooling + +It is ***strongly*** recommended that database connection pooling be used in a production environment. The following configuration makes use of the c3p0 connection pooling library, which would replace the `dataSource` bean found in the `ticketRegistry.xml` example above: + +{% highlight xml %} +... + +... +{% endhighlight %} + +The following pool configuration parameters are provided for information only and may serve as a reasonable starting point for configuring a production database connection pool. + +
Usage Tip

Note the health check query is specific to PostgreSQL.

+ +{% highlight bash %} +# == Basic database connection pool configuration == +database.dialect=org.hibernate.dialect.PostgreSQLDialect +database.driverClass=org.postgresql.Driver +database.url=jdbc:postgresql://somehost.vt.edu/cas?ssl=true +database.user=somebody +database.password=meaningless +database.pool.minSize=6 +database.pool.maxSize=18 + +# Maximum amount of time to wait in ms for a connection to become +# available when the pool is exhausted +database.pool.maxWait=10000 + +# Amount of time in seconds after which idle connections +# in excess of minimum size are pruned. +database.pool.maxIdleTime=120 + +# Number of connections to obtain on pool exhaustion condition. +# The maximum pool size is always respected when acquiring +# new connections. +database.pool.acquireIncrement=6 + +# == Connection testing settings == + +# Period in s at which a health query will be issued on idle +# connections to determine connection liveliness. +database.pool.idleConnectionTestPeriod=30 + +# Query executed periodically to test health +database.pool.connectionHealthQuery=select 1 + +# == Database recovery settings == + +# Number of times to retry acquiring a _new_ connection +# when an error is encountered during acquisition. +database.pool.acquireRetryAttempts=5 + +# Amount of time in ms to wait between successive aquire retry attempts. +database.pool.acquireRetryDelay=2000 +{% endhighlight %} + +The following maven dependency for the library must be included in your Maven overlay: +{% highlight xml %} + + c3p0 + c3p0 + ${c3p0.version} + runtime + +{% endhighlight %} + + +# Platform Considerations + +## MySQL + +### Use InnoDB Tables +The use of InnoDB tables is strongly recommended for the MySQL platform for a couple reasons: + +- InnoDB provides referential integrity that is helpful for preventing orphaned records in ticket tables. +- Provides better locking semantics (e.g. support for SELECT ... FOR UPDATE) than the default MyISAM table type. + +InnoDB tables are easily specified via the use of the following Hibernate dialect: + +{% highlight xml %} +org.hibernate.dialect.MySQLInnoDBDialect + + +org.hibernate.dialect.MySQL5InnoDBDialect +{% endhighlight %} + + +###BLOB vs LONGBLOB +Hibernate on recent versions of MySQL (e.g. 5.1) properly maps the `@Lob` JPA annotation onto type `LONGBLOB`, which is very important since these fields commonly store serialized graphs of Java objects that grow proportionally with CAS SSO session lifetime. Under some circumstances, Hibernate may treat these columns as type `BLOB`, which have storage limits that are easily exceeded. It is recommended that the generated schema be reviewed and any BLOB type columns be converted to `LONGBLOB`. + +The following MySQL statement would change this `SERVICES_GRANTED_ACCESS_TO` column's type to `LONGBLOB`: + +{% highlight sql %} +ALTER TABLE TICKETGRANTINGTICKET MODIFY SERVICES_GRANTED_ACCESS_TO LONGBLOB; +{% endhighlight %} + + +###Case Sensitive Schema +It may necessary to force lowercase schema names in the MySQL configuration: + +Adjust the `my.cnf` file to include the following: +{% highlight bash %} +lower-case-table-names = 1 +{% endhighlight %} + + + + + + + + + + diff --git a/cas-server-documentation/installation/LDAP-Authentication.md b/cas-server-documentation/installation/LDAP-Authentication.md new file mode 100644 index 000000000000..bd3012c90584 --- /dev/null +++ b/cas-server-documentation/installation/LDAP-Authentication.md @@ -0,0 +1,660 @@ +--- +layout: default +title: CAS - LDAP Authentication +--- + +# LDAP Authentication +LDAP integration is enabled by including the following dependency in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-support-ldap + ${cas.version} + +{% endhighlight %} + +`LdapAuthenticationHandler` authenticates a username/password against an LDAP directory such as Active Directory +or OpenLDAP. There are numerous directory architectures and we provide configuration for four common cases: + +1. [Active Directory](#active-directory-authentication) - Users authenticate with _sAMAAccountName_. +2. [Authenticated Search](#ldap-requiring-authenticated-search) - Manager bind/search followed by user simple bind. +3. [Anonymous Search](#ldap-supporting-anonymous-search) - Anonymous search followed by user simple bind. +4. [Direct Bind](#ldap-supporting-direct-bind) - Compute user DN from format string and perform simple bind. +5. [Principal Attributes Retrieval](#ldap-authentication-principal-attributes) - Resolve principal attributes directly as part of LDAP authentication. + +See the [ldaptive documentation](http://www.ldaptive.org/) for more information or to accommodate other situations. + +## Ldap Authentication Principal Attributes +The `LdapAuthenticationHandler` is capable of resolving and retrieving principal attributes independently without the need for [extra principal resolver machinery](../integration/Attribute-Resolution.html). + +{% highlight xml %} + + + + + + + + + +{% endhighlight %} + +The above configuration defines a map of attribtues. Keys are LDAP attribute names and values are CAS attribute names which allow you to optionally, retrieve a given attribute and release it under a virtual name. (i.e. Retrieve `mail` from LDAP and remap/rename it to `email` to be released later). If you have no need for this virtual mapping mechanism, you could directly specify the attributes as a list, in which case the above configuration would become: + +{% highlight xml %} + + + + displayName + mail + memberOf + + + +{% endhighlight %} + + +## Active Directory Authentication +The following configuration authenticates users by _sAMAccountName_ without performing a search, +which requires manager/administrator credentials in most cases. It is therefore the most performant and secure +solution for the typical Active Directory deployment. Note that the resolved principal ID, which becomes the NetID +passed to CAS client applications, is the _sAMAccountName_ in the following example. +Simply copy the configuration to `deployerConfigContext.xml` and provide values for property placeholders. +{% highlight xml %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{% endhighlight %} + + +#### LDAP Requiring Authenticated Search +The following configuration snippet provides a template for LDAP authentication performed with manager credentials +followed by a bind. Copy the configuration to `deployerConfigContext.xml` and provide values for property placeholders. +{% highlight xml %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{% endhighlight %} + + +## LDAP Supporting Anonymous Search +The following configuration snippet provides a template for LDAP authentication performed with an anonymous search +followed by a bind. Copy the configuration to `deployerConfigContext.xml` and provide values for property placeholders. +{% highlight xml %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{% endhighlight %} + + +## LDAP Supporting Direct Bind +The following configuration snippet provides a template for LDAP authentication where no search is required to +compute the DN needed for a bind operation. There are two requirements for this use case: + +1. All users are under a single branch in the directory, e.g. `ou=Users,dc=example,dc=org`. +2. The username provided on the CAS login form is part of the DN, e.g. `uid=%s,ou=Users,dc=exmaple,dc=org`. + +Copy the configuration to `deployerConfigContext.xml` and provide values for property placeholders. +{% highlight xml %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{% endhighlight %} + +## LDAP Provider Configuration +In certain cases, it may be desirable to use a specific provider implementation when +attempting to establish connections to LDAP. In order to do this, the `connectionFactory` +configuration must be modified to include a reference to the selected provider. + +Here's an example for configuring an UnboundID provider for a given connection factory: + +{% highlight xml %} +... + + + +... +{% endhighlight %} + +Note that additional dependencies must be available to CAS at runtime, so it's able to locate +the provider implementation and supply that to connections. + +## LDAP Properties Starter +The following LDAP configuration properties provide a reasonable starting point for configuring the LDAP +authentication handler. The `ldap.url` property must be changed at a minumum. LDAP properties may be added to the +`cas.properties` configuration file; alternatively they may be isolated in an `ldap.properties` file and loaded +into the Spring application context by modifying the `propertyFileConfigurer.xml` configuration file. + + #======================================== + # General properties + #======================================== + ldap.url=ldap://directory.ldaptive.org + + # LDAP connection timeout in milliseconds + ldap.connectTimeout=3000 + + # Whether to use StartTLS (probably needed if not SSL connection) + ldap.useStartTLS=true + + #======================================== + # LDAP connection pool configuration + #======================================== + ldap.pool.minSize=3 + ldap.pool.maxSize=10 + ldap.pool.validateOnCheckout=false + ldap.pool.validatePeriodically=true + + # Amount of time in milliseconds to block on pool exhausted condition + # before giving up. + ldap.pool.blockWaitTime=3000 + + # Frequency of connection validation in seconds + # Only applies if validatePeriodically=true + ldap.pool.validatePeriod=300 + + # Attempt to prune connections every N seconds + ldap.pool.prunePeriod=300 + + # Maximum amount of time an idle connection is allowed to be in + # pool before it is liable to be removed/destroyed + ldap.pool.idleTime=600 + + #======================================== + # Authentication + #======================================== + + # Base DN of users to be authenticated + ldap.authn.baseDn=ou=Users,dc=example,dc=org + + # Manager DN for authenticated searches + ldap.authn.managerDN=uid=manager,ou=Users,dc=example,dc=org + + # Manager password for authenticated searches + ldap.authn.managerPassword=nonsense + + # Search filter used for configurations that require searching for DNs + #ldap.authn.searchFilter=(&(uid={user})(accountState=active)) + ldap.authn.searchFilter=(uid={user}) + + # Search filter used for configurations that require searching for DNs + #ldap.authn.format=uid=%s,ou=Users,dc=example,dc=org + ldap.authn.format=%s@example.com + + +## LDAP Password Policy Enforcement +The purpose of the LPPE is twofold: + +- Detect a number of scenarios that would otherwise prevent user authentication, specifically using an Ldap instance as the primary source of user accounts. +- Warn users whose account status is near a configurable expiration date and redirect the flow to an external identity management system. + +### Reflecting LDAP Account Status +The below scenarios are by default considered errors preventing authentication in a generic manner through the normal CAS login flow. LPPE intercepts the authentication flow, detecting the above standard error codes. Error codes are then translated into proper messages in the CAS login flow and would allow the user to take proper action, fully explaining the nature of the problem. + +- `ACCOUNT_LOCKED` +- `ACCOUNT_DISABLED` +- `INVALID_LOGON_HOURS` +- `INVALID_WORKSTATION` +- `PASSWORD_MUST_CHANGE` +- `PASSWORD_EXPIRED` + +The translation of LDAP errors into CAS workflow is all handled by [ldaptive](http://www.ldaptive.org/docs/guide/authentication/accountstate). + +### Account Expiration Notification +LPPE is also able to warn the user when the account is about to expire. The expiration policy is determined through pre-configured Ldap attributes with default values in place. + +
No Password Management!

LPPE is not about password management. If you are looking for that sort of capability integrating with CAS, you might be interested in: + +

+ +## Configuration +LPPE is by default turned off. To enable the functionally, navigate to `src/main/webapp/WEB-INF/unused-spring-configuration` and move the `lppe-configuration.xml` xml file over to the `spring-configuration` directory. + +{% highlight xml %} + + + + +{% endhighlight %} + +Next, in your `ldapAuthenticationHandler` bean, configure the password policy configuration above: + +{% highlight xml %} + + + ... + +{% endhighlight %} + +Next, you have to explicitly define an LDAP-specific response handler in your `Authenticator`. + +### Generic + +{% highlight xml %} + + + + + + + + +{% endhighlight %} + +Also, you have to handle the `PasswordPolicy` controls in the `BindAuthenticationHandler`: + +{% highlight xml %} + + + + + + + +{% endhighlight %} + +### Active Directory + +{% highlight xml %} + + + + + + + +{% endhighlight %} + +### Components + +#### `DefaultAccountStateHandler` +The default account state handler, that calculates the password expiration warning period, maps LDAP errors to the CAS workflow. + +#### `OptionalWarningAccountStateHandler` +Supports both opt-in and opt-out warnings on a per-user basis. + +{% highlight xml %} + +{% endhighlight %} + +The first two parameters define an attribute on the user entry to match on, and the third parameter determines +whether password expiration warnings should be displayed on match. + +**Note:** Deployers MUST configure LDAP components to provide `warningAttributeName` in the set of attributes returned from the LDAP query for user details. + +## Troubleshooting +To enable additional logging, modify the log4j configuration file to add the following: + +{% highlight xml %} + + + + +{% endhighlight %} + diff --git a/cas-server-documentation/installation/Legacy-Authentication.md b/cas-server-documentation/installation/Legacy-Authentication.md new file mode 100644 index 000000000000..4e4db1c33eb1 --- /dev/null +++ b/cas-server-documentation/installation/Legacy-Authentication.md @@ -0,0 +1,40 @@ +--- +layout: default +title: CAS - Legacy Authentication +--- + +# Legacy Authentication +Legacy authentication components are enabled by including the following dependencies in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-support-legacy + ${cas.version} + +{% endhighlight %} + +## Legacy Components +CAS provides the following components to accommodate different legacy authentication needs for backwards compatibility: + + +###`LegacyAuthenticationHandlerAdapter` +Adapts a CAS 3.x `AuthenticationHandler` onto a CAS 4.x `AuthenticationHandler`. If the supplied legacy authentication handler supports `NamedAuthenticationHandler`, then its defined name will be used to identify the handler. Otherwise, the name of the class itself will be used. + + +###`CredentialsAdapter` +Interface to be implemented by adapters to determine how credentials need be converted over to CAS 4. + + +####`UsernamePasswordCredentialsAdapter` +Adapts and converts a CAS 4 username/password credential into a CAS 3.x username/password credential. + + +### Sample Configuration + +{% highlight xml %} + +{% endhighlight %} diff --git a/cas-server-documentation/installation/Logging.md b/cas-server-documentation/installation/Logging.md new file mode 100644 index 000000000000..d6949364b401 --- /dev/null +++ b/cas-server-documentation/installation/Logging.md @@ -0,0 +1,254 @@ +--- +layout: default +title: CAS - Logging Configuration +--- + + +#Logging +CAS provides a logging facility that logs important informational events like authentication success and failure; it can be customized to produce additional information for troubleshooting. CAS uses the Slf4J Logging framework as a facade for the [Log4J engine](http://logging.apache.org‎) by default. + +The log4j configuration file is located in `cas-server-webapp/src/main/webapp/WEB-INF/classes/log4j2.xml`. By default logging is set to `INFO` for all functionality related to `org.jasig.cas` code and `WARN` for messages related to Spring framework, etc. For debugging and diagnostic purposes you may want to set these levels to `DEBUG`. + +{% highlight xml %} +... + + + + + + + +... +{% endhighlight %} + +
Usage Warning!

When in production though, you probably want to run them both as `WARN`.

+ + +##Components +The log4j configuration is by default loaded using the following components at `cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/log4jConfiguration.xml`: + +{% highlight xml %} + +{% endhighlight %} + +It is often time helpful to externalize `log4j2.xml` to a system path to preserve settings between upgrades. The location of `log4j2.xml` file by default is on the runtime classpath and at minute intervals respective. These may be overridden by the `cas.properties` file + +{% highlight bash %} +# log4j.config.location=classpath:log4j2.xml +{% endhighlight %} + + +##Configuration +The `log4j2.xml` file by default at `WEB-INF/classes` provides the following `appender` elements that decide where and how messages from components should be displayed. Two are provided by default that output messages to the system console and a `cas.log` file: + +###Refresh Interval +The `log4j2.xml` itself controls the refresh interval of the logging configuration. Log4j has the ability to automatically detect changes to the configuration file and reconfigure itself. If the `monitorInterval` attribute is specified on the configuration element and is set to a non-zero value then the file will be checked the next time a log event is evaluated and/or logged and the `monitorInterval` has elapsed since the last check. This will allow you to adjust the log levels and configuration without restarting the server environment. + +{% highlight xml %} + + + + ... +{% endhighlight %} + +###Appenders +{% highlight xml %} + + + + + + + + + + + +{% endhighlight %} + + +###Loggers +Additional loggers are available to specify the logging level for component categories. + +{% highlight xml %} + + + + + + + + + + + + + + + + + + +{% endhighlight %} + +If you wish enable another package for logging, you can simply add another `Logger` element to the configuration. Here is an example: + +{% highlight xml %} + + + + +{% endhighlight %} + +##Log Data Sanitation +For security purposes, CAS by default will attempt to remove TGT and PGT ids from all log data. This will of course include messages that are routed to a log destination by the logging framework as well as all audit messages. A sample follows below: + +{% highlight bash %} +============================================================= +WHO: audit:unknown +WHAT: TGT-****************************************************123456-cas01.example.org +ACTION: TICKET_GRANTING_TICKET_DESTROYED +APPLICATION: CAS +WHEN: Sat Jul 12 04:10:35 PDT 2014 +CLIENT IP ADDRESS: ... +SERVER IP ADDRESS: ... +============================================================= +{% endhighlight %} + +Certain number of characters are left at the trailing end of the ticket id to assist with troubleshooting and diagnostics. This is achieved by providing a specific binding for the SLF4j configuration. + + +#Audits +CAS uses the [Inspektr framework](https://github.com/Jasig/inspektr) for auditing purposes and statistics. The Inspektr project allows for non-intrusive auditing and logging of the coarse-grained execution paths e.g. Spring-managed beans method executions by using annotations and Spring-managed `@Aspect`-style aspects. + +##Components + +###`AuditTrailManagementAspect` +Aspect modularizing management of an audit trail data concern. + + +###`Slf4jLoggingAuditTrailManager` +`AuditTrailManager` that dumps auditable information to a configured logger based on SLF4J, at the `INFO` level. + + +###`JdbcAuditTrailManager` +`AuditTrailManager` to persist the audit trail to the `AUDIT_TRAIL` table in a rational database. + + +###`TicketAsFirstParameterResourceResolver` +`ResourceResolver` that can determine the ticket id from the first parameter of the method call. + + +###`TicketOrCredentialPrincipalResolver` +`PrincipalResolver` that can retrieve the username from either the `Ticket` or from the `Credential`. + +##Configuration +Audit functionality is specifically controlled by the `WEB-INF/spring-configuration/auditTrailContext.xml`. Configuration of the audit trail manager is defined inside `deployerConfigContext.xml`. + + +###Database Audits +By default, audit messages appear in log files via the `Slf4jLoggingAuditTrailManager`. If you intend to use a database for auditing functionality, adjust the audit manager to match the sample configuration below: +{% highlight xml %} + + + + + + + + + +{% endhighlight %} + +You'll need to have a `dataSource` that defines a connection to your database. The following snippet +demonstrates a data source that connects to HSQLDB v1.8: + +{% highlight xml %} + +{% endhighlight %} + +In order to configure the `dataSource` you will furthermore need additional dependencies +in the `pom.xml` file that deal with creating connections. + +{% highlight xml %} + + commons-dbcp + commons-dbcp + ${dbcp.version} + runtime + + + commons-pool + commons-pool + ${commons.pool.version} + runtime + + + + org.hsqldb + hsqldb + ${hsqldb.version} + runtime + +{% endhighlight %} + +You will also need the dependency for the database driver that you have chosen. + +Finally, the following database table needs to be created beforehand: + +{% highlight sql %} +CREATE TABLE COM_AUDIT_TRAIL +( + AUD_USER VARCHAR(100) NOT NULL, + AUD_CLIENT_IP VARCHAR(15) NOT NULL, + AUD_SERVER_IP VARCHAR(15) NOT NULL, + AUD_RESOURCE VARCHAR(100) NOT NULL, + AUD_ACTION VARCHAR(100) NOT NULL, + APPLIC_CD VARCHAR(15) NOT NULL, + AUD_DATE TIMESTAMP NOT NULL +); +{% endhighlight %} + +You may need to augment the syntax and column types per your specific database implementation. + +##Sample Log Output +{% highlight bash %} +WHO: org.jasig.cas.support.oauth.authentication.principal.OAuthCredentials@6cd7c975 +WHAT: supplied credentials: org.jasig.cas.support.oauth.authentication.principal.OAuthCredentials@6cd7c975 +ACTION: AUTHENTICATION_SUCCESS +APPLICATION: CAS +WHEN: Mon Aug 26 12:35:59 IST 2013 +CLIENT IP ADDRESS: 172.16.5.181 +SERVER IP ADDRESS: 192.168.200.22 + +WHO: org.jasig.cas.support.oauth.authentication.principal.OAuthCredentials@6cd7c975 +WHAT: TGT-9-qj2jZKQUmu1gQvXNf7tXQOJPOtROvOuvYAxybhZiVrdZ6pCUwW-cas01.example.org +ACTION: TICKET_GRANTING_TICKET_CREATED +APPLICATION: CAS +WHEN: Mon Aug 26 12:35:59 IST 2013 +CLIENT IP ADDRESS: 172.16.5.181 +SERVER IP ADDRESS: 192.168.200.22 +{% endhighlight %} diff --git a/cas-server-documentation/installation/Logout-Single-Signout.md b/cas-server-documentation/installation/Logout-Single-Signout.md new file mode 100644 index 000000000000..1b49cc547fa8 --- /dev/null +++ b/cas-server-documentation/installation/Logout-Single-Signout.md @@ -0,0 +1,223 @@ +--- +layout: default +title: CAS - Logout & Single Logout +--- + + + +#Logout and Single Logout (SLO) + +There are potentially many active application sessions during a CAS single sign-on session, and the distinction between +logout and single logout is based on the number of sessions that are ended upon a _logout_ operation. The scope of logout +is determined by where the action takes place: + +1. Application logout - ends a single application session +2. CAS logout - ends the CAS SSO session + +Note that the logout action in each case has no effect on the other in the simple case. Ending an application session +does not end the CAS session and ending the CAS session does not affect application sessions. This is a common cause of +confusion for new users and deployers of an SSO system. + +The single logout support in CAS attempts to reconcile the dispartity between CAS logout and application logout. When +CAS is configured for SLO, it attempts to send logout messages to every application that requested authentication to +CAS during the SSO session. While this is a best-effort process, in many cases it works well and provides a consistent +user experience by creating symmetry between login and logout. + + +##CAS Logout + +Per the [CAS Protocol](../protocol/CAS-Protocol.html), the `/logout` endpoint is responsible for destroying the current SSO session. Upon logout, it may also be desirable to redirect back to a service. This is controlled via specifying the redirect link via the `service` parameter. + +The redirect behavior is turned off by default, and is activated via the following setting in `cas.properties`: + +{% highlight bash %} +# Specify whether CAS should redirect to the specified service parameter on /logout requests +# cas.logout.followServiceRedirects=false +{% endhighlight %} + +The specified url must be registered in the service registry of CAS and enabled. + +##Web Session Termination +By default, CAS comes with a `TerminateWebSessionListener` whose job is to expire the web session once the webflow has ended. The goal is to clean up the session as soon as possible to decrease memory consumption. + +The listener configures the maximum inactivity interval for the web session, which is the time, in seconds, between client requests before the servlet container will invalidate this session. An interval value of zero or less indicates that the session should never timeout. This value can be controlled via the following setting in `cas.properties`: + +{% highlight bash %} +# Specifies the time, in seconds, to invalidate the web session. +# terminate.web.session.timeout=2 +{% endhighlight %} + +##Single Logout (SLO) +CAS is designed to support single sign out: it means that it will be able to invalidate client application sessions in addition to its own SSO session. +Whenever a ticket-granting ticket is explicitly expired, the logout protocol will be initiated. Clients that do not support the logout protocol may notice extra requests in their access logs that appear not to do anything. + +
Usage Warning!

Single Logout is turned on by default.

+ +When a CAS session ends, it notifies each of the services that the SSO session is no longer valid, and that relying parties need to invalidate their own session. + +This can happen in two ways: + +1. CAS sends an HTTP POST message directly to the service ( _back channel_ communication): this is the traditional way of performing notification to the service. +2. CAS redirects (HTTP 302) to the service with a message and a _RelayState_ parameter (_front channel_ communication): This feature is inspired by SAML SLO, and is needed if the client application is composed of several servers and use session affinity. The expected behaviour of the CAS client is to invalidate the application web session and redirect back to the CAS server with the _RelayState_ parameter. + +
Usage Warning!

Front-channel SLO at this point is still experimental.

+ +##SLO Requests +The way the notification is done (_back_ or _front_ channel) is configured at a service level through the `logoutType` property. This value is set to `LogoutType.BACK_CHANNEL` by default. The message is delivered or the redirection is sent to the URL presented in the _service_ parameter of the original CAS protocol ticket request. + +A sample SLO message: + +{% highlight xml %} + + @NOT_USED@ + [SESSION IDENTIFIER] + +{% endhighlight %} + +The session identifier is the CAS service ticket ID that was provided to the service when it originally authenticated +to CAS. The session identifier is used to correlate a CAS session with an application session; for example, the SLO +session identifier maps to a servlet session that can subsequently be destroyed to terminate the application session. + +Logout protocol is effectively managed by the `LogoutManagerImpl` component: + +{% highlight xml %} + +{% endhighlight %} + + +###Turning Off Single Logout +To disable single logout, adjust the following setting in `cas.properties` file: + +{% highlight bash %} +# To turn off all back channel SLO requests set slo.disabled to true +# slo.callbacks.disabled=false +{% endhighlight %} + +###Single Logout Per Service +Registered applications with CAS have the option to control single logout behavior individually via the [Service Managament](Service-Management.html) component. Each registered service in the service registry will include configuration that describes how to the logout request should be submitted. This behavior is controlled via the `logoutType` property which allows to specify whether the logout request should be submitted via back/front channel or turned off for this application. + +Sample configuration follows: + +{% highlight xml %} + + + + + + + +{% endhighlight %} + +###Service Endpoint for Logout Requests +By default, logout requests are submitted to the original service id. CAS has the option to submit such requests to a specific service endpoint that is different +from the original service id, and of course can be configured on a per-service level. This is useful in cases where the application that is integrated with CAS +does not exactly use a CAS client that supports intercepting such requests and instead, exposes a different endpoint for its logout operations. + +To configure a service specific endpoint, try the following example: + +{% highlight xml %} + + + + + + +{% endhighlight %} + + +###Aynchronous SLO Messages +By default, backchannel logout messages are sent to endpoint in an asynchronous fashion. To allow synchronous messages, modify the following setting in `cas.properties`: + +{% highlight bash %} +# To send callbacks to endpoints synchronously, set this to false +# slo.callbacks.asynchronous=true +{% endhighlight %} + + +###Ticket Registry Cleaner Behavior +Furthermore, the default behavior is to issue single sign out callbacks in response to a logout request or when a TGT is expired via expiration policy when a `TicketRegistryCleaner` runs. If you are using ticket registry cleaner and you want to enable the single sign out callback only when CAS receives a logout request, you can configure your `TicketRegistryCleaner` as such: + +{% highlight xml %} + +{% endhighlight %} + +Note that certain ticket registries don't use or need a registry cleaner. For such registries, the option of having a ticker registry cleaner is entirely done away with and is currently not implemented. With that being absent, you will no longer receive automatic SLO callbacks upon TGT expiration. As such, the only thing that would reach back to the should then be explicit logout requests per the CAS protocol. + + +####With `TicketRegistryCleaner` +1. Single Logout is turned on +2. The cleaner runs to detect the ticket that are automatically expired. It will query the tickets in the ticket registry, and will accumulate those that are expired. +3. For the collection of expired tickets, the cleaner will again ask them to "expire" which triggers the SLO callback to be issued. +4. The cleaner subsequently removes the TGT from the registry. Note that simply removing a ticket by itself from the registry does not issue the SLO callback. A ticket needs to be explicitly told one way or another, to "expire" itself: + - If the ticket is already expired, the mechanism will issue the SLO callback. + - If the ticket is not already expired, it will be marked as expired and the SLO callback will be issued. + + +####Without `TicketRegistryCleaner` +1. Single Logout is turned on +2. There is no cleaner, so nothing runs in the background or otherwise to "expire" and delete tickets from the registry and thus, no SLO callbacks will be issued automatically. +2. A logout request is received by CAS +3. CAS will locate the TGT and will attempt to destroy the SSO session. +4. In destroying the ticket, CAS will: + - Ask the ticket to expire itself, which will issue SLO callbacks. + - Delete the ticket from the registry + +## SSO Session vs. Application Session +In order to better understand the SSO session management of CAS and how it regards application sessions, one important note is to be first and foremost considered: + +
CAS is NOT a session manager

Application session is the responsibility of the application.

+ +CAS wants to maintain and control the SSO session in the form of +the `TicketGrantingTicket` and a TGT id which is shared between the +user-agent and the CAS server in the form of a secure cookie. + +CAS is not an application session manager in that it is the +responsibility of the applications to maintain and control their own +application sessions. Once authentication is completed, CAS is +typically out of the picture in terms of the application sessions. Therefore, the expiration policy +of the application session itself is entirely independent of CAS and may be loosely coordinated +and adjusted depending on the ideal user experience in the event that the application session expires. + +In the event that Single Logout is not activated, typically, application may expose a logout endpoint in order to destroy the session and next, redirect +the agent to the CAS `logout` endpoint in order to completely destroy the SSO session as well. + +Here's a brief diagram that demonstrates various application session configuration and interactions with CAS: + +![](http://i.imgur.com/0XyuLgz.png) diff --git a/cas-server-documentation/installation/Maven-Overlay-Installation.md b/cas-server-documentation/installation/Maven-Overlay-Installation.md new file mode 100644 index 000000000000..1f2d09a8bd26 --- /dev/null +++ b/cas-server-documentation/installation/Maven-Overlay-Installation.md @@ -0,0 +1,335 @@ +--- +layout: default +title: CAS - Maven Overlay Installation +--- + +# Maven Overlay Installation +CAS installation is a fundamentally source-oriented process, and we recommend a +[Maven WAR overlay](http://maven.apache.org/plugins/maven-war-plugin/overlays.html) project to organize +customizations such as component configuration and UI design. +The output of a Maven WAR overlay build is a `cas.war` file that can be deployed on a Java servlet container like +[Tomcat](http://tomcat.apache.org/whichversion.html). + +A simple Maven WAR overlay project is provided for reference and study: +[https://github.com/UniconLabs/simple-cas4-overlay-template](https://github.com/UniconLabs/simple-cas4-overlay-template) + +The following list of CAS components are those most often customized by deployers: + +1. Authentication handlers (i.e. `LdapAuthenticationHandler`) +2. Storage backend (i.e. `MemcachedTicketRegistry`) +3. View layer files (JSP/CSS/Javascript) + +The first two are controlled by modifying Spring XML configuration files under +`src/main/webapp/WEB-INF/spring-configuration`, the latter by modifying JSP and CSS files under +`src/main/webapp/WEB-INF/view/jsp/default` in the Maven WAR overlay project. Every aspect of CAS can be controlled by +adding, removing, or modifying files in the overlay; it's also possible and indeed common to customize the behavior of +CAS by adding third-party components that implement CAS APIs as Java source files or dependency references. + +Once an overlay project has been created, the `cas.war` file must be built and subsequently deployed into a Java +servlet container like Tomcat. The following set of commands, issued from the Maven WAR overlay project root +directory, provides a sketch of how to accomplish this on a Unix platform. + +{% highlight bash %} +$CATALINA_HOME/bin/shutdown.sh +mvn clean package +rm -rf $CATALINA_HOME/logs/* +rm -f $CATALINA_HOME/webapps/cas.war +rm -rf $CATALINA_HOME/webapps/cas +rm -rf $CATALINA_HOME/work/* +cp -v target/cas.war $CATALINA_HOME/webapps +$CATALINA_HOME/bin/startup.sh +{% endhighlight %} + + +## Configuration Files +CAS configuration is controlled primarily by Spring XML context configuration files. At a minimum, every deployer +must customize `deployerConfigContext.xml` and `cas.properties` by including them in the Maven WAR overlay, +but there are other optional configuration files that may be included in the overlay for further customization +or to provide additional features. The following exploded file system hierarchy shows how files should be organized +in the overlay (1): + +{% highlight bash %} + +\---src + +---main + | +---resources + | | apereo.properties + | | cas-theme-default.properties + | | log4j.xml + | | messages.properties + | | protocol_views.properties + | | saml_views.properties + | | truststore.jks + | | + | \---webapp + | | favicon.ico + | | index.jsp + | | + | +---css + | | cas.css + | | + | +---images + | | cas-logo.png + | | confirm.gif + | | error.gif + | | error.png + | | green.gif + | | info.gif + | | info.png + | | ja-sig-logo.gif + | | jasig-logo-small.png + | | jasig-logo.png + | | key-point_bl.gif + | | key-point_br.gif + | | key-point_tl.gif + | | key-point_tr.gif + | | question.png + | | red.gif + | | success.png + | | warning.png + | | + | +---js + | | cas.js + | | + | +---themes + | | \---apereo + | | +---css + | | | cas.css + | | | + | | +---images + | | | apereo-logo.png + | | | bg-tile.gif + | | | + | | \---js + | | cas.js + | | + | \---WEB-INF + | | cas-servlet.xml + | | cas.properties + | | deployerConfigContext.xml + | | restlet-servlet.xml + | | web.xml + | | + | +---spring-configuration + | | applicationContext.xml + | | argumentExtractorsConfiguration.xml + | | auditTrailContext.xml + | | filters.xml + | | log4jConfiguration.xml + | | propertyFileConfigurer.xml + | | README.txt + | | securityContext.xml + | | ticketExpirationPolicies.xml + | | ticketGrantingTicketCookieGenerator.xml + | | ticketRegistry.xml + | | uniqueIdGenerators.xml + | | warnCookieGenerator.xml + | | + | +---unused-spring-configuration + | | clearpass-configuration.xml + | | lppe-configuration.xml + | | mbeans.xml + | | + | +---view + | | \---jsp + | | | authorizationFailure.jsp + | | | errors.jsp + | | | + | | +---default + | | | \---ui + | | | | casAccountDisabledView.jsp + | | | | casAccountLockedView.jsp + | | | | casBadHoursView.jsp + | | | | casBadWorkstationView.jsp + | | | | casConfirmView.jsp + | | | | casExpiredPassView.jsp + | | | | casGenericSuccess.jsp + | | | | casLoginMessageView.jsp + | | | | casLoginView.jsp + | | | | casLogoutView.jsp + | | | | casMustChangePassView.jsp + | | | | serviceErrorSsoView.jsp + | | | | serviceErrorView.jsp + | | | | + | | | \---includes + | | | bottom.jsp + | | | top.jsp + | | | + | | +---monitoring + | | | viewStatistics.jsp + | | | + | | \---protocol + | | | casPostResponseView.jsp + | | | + | | +---2.0 + | | | casProxyFailureView.jsp + | | | casProxySuccessView.jsp + | | | casServiceValidationFailure.jsp + | | | casServiceValidationSuccess.jsp + | | | + | | +---3.0 + | | | casServiceValidationFailure.jsp + | | | casServiceValidationSuccess.jsp + | | | + | | +---clearPass + | | | clearPassFailure.jsp + | | | clearPassSuccess.jsp + | | | + | | +---oauth + | | | confirm.jsp + | | | + | | \---openid + | | casOpenIdAssociationFailureView.jsp + | | casOpenIdAssociationSuccessView.jsp + | | casOpenIdServiceFailureView.jsp + | | casOpenIdServiceSuccessView.jsp + | | user.jsp + | | + | \---webflow + | +---login + | | login-webflow.xml + | | + | \---logout + | logout-webflow.xml + | +{% endhighlight %} + +The approach to Spring configuration is to group related components into a single configuration file, which allows +deployers to include the handful of files containing components (typically authentication and ticketing) required +for their environment. The files are intended to be self-identifying with respect to the kinds of components they +contain, with the exception of `applicationContext.xml` and `cas-servlet.xml`. For example, `auditTrailContext.xml` +contains components related to the CAS audit trail where events are emitted for successful and failed authentication attempts, among other kinds of auditable events. + +It is common practice to exclude `cas.properties` from the overlay and place it at a well-known filesystem location +outside the WAR deployable. In that case, `propertyFileConfigurer.xml` must be configured to point to the filesystem +location of `cas.properties`. Generally, the Spring XML configuration files under `spring-configuration` are the most +common configuration files, beyond `deployerConfigContext.xml`, to be included in an overlay. The supplementary Spring +configuration files are organized into logically separate configuration concerns that are clearly indicated by the file +name. + +CAS uses Spring Webflow to drive the login process in a modular and configurable fashion; the `login-webflow.xml` +file contains a straightforward description of states and transitions in the flow. Customizing this file is probably +the most common configuration concern beyond component configuration in the Spring XML configuration files. See the +Spring Webflow Customization Guide for a thorough description of the various CAS flows and discussion of common +configuration points. + +## Spring Configuration +CAS server depends heavily on the Spring framework. There are exact and specific XML configuration files under `spring-configuration` directory that control various properties of CAS as well as `cas-servlet.xml` and `deployerConfigContext.xml` the latter of which is mostly expected by CAS adopters to be included in the overlay for environment-specific CAS settings. + +Spring beans in the XML configuration files can be overwritten to change behavior if need be via the Maven overlay process. There are two approaches to this: + +1. The XML file can be obtained from source for the CAS version and placed at the same exact path by the same exact name in the Maven overlay build. If configured correctly, the build will use the locally-provided XML file rather than the default. +2. CAS server is able to load patterns of XML configuration files to overwrite what is provided by default. These configuration files that intend to overrule CAS default behavior can be placed at `/WEB-INF/` and must be named by the following pattern: `cas-servlet-*.xml`. Beans placed in this file will overwrite others. This configuration is recognized by the `DispatcherServlet` in the `web.xml` file: + +{% highlight xml %} +... + + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/cas-servlet.xml, /WEB-INF/cas-servlet-*.xml + + +... +{% endhighlight %} + +## Custom and Third-Party Source +It is common to customize or extend the functionality of CAS by developing Java components that implement CAS APIs or +to include third-party source by Maven dependency references. Including third-party source is trivial; simply include +the relevant dependency in the overlay `pom.xml` file. In order to include custom Java source, it should be included +under a `src/java/main` directory in the overlay project source tree. + + ├── src + │   ├── main + │   │   ├── java + │   │   │   └── edu + │   │   │   └── vt + │   │   │   └── middleware + │   │   │   └── cas + │   │   │   ├── audit + │   │   │   │   ├── CompactSlf4jAuditTrailManager.java + │   │   │   │   ├── CredentialsResourceResolver.java + │   │   │   │   ├── ServiceResourceResolver.java + │   │   │   │   └── TicketOrCredentialPrincipalResolver.java + │   │   │   ├── authentication + │   │   │   │   └── principal + │   │   │   │   ├── AbstractCredentialsToPrincipalResolver.java + │   │   │   │   ├── PDCCredentialsToPrincipalResolver.java + │   │   │   │   └── UsernamePasswordCredentialsToPrincipalResolver.java + │   │   │   ├── services + │   │   │   │   └── JsonServiceRegistryDao.java + │   │   │   ├── util + │   │   │   │   └── X509Helper.java + │   │   │   └── web + │   │   │   ├── HelpController.java + │   │   │   ├── RegisteredServiceController.java + │   │   │   ├── StatsController.java + │   │   │   ├── WarnController.java + │   │   │   ├── flow + │   │   │   │   ├── AbstractForgottenCredentialAction.java + │   │   │   │   ├── AbstractLdapQueryAction.java + │   │   │   │   ├── AffiliationHandlerAction.java + │   │   │   │   ├── CheckAccountRecoveryMaintenanceAction.java + │   │   │   │   ├── CheckPasswordExpirationAction.java + │   │   │   │   ├── ForgottenCredentialTypeAction.java + │   │   │   │   ├── LookupRegisteredServiceAction.java + │   │   │   │   ├── NoSuchFlowHandler.java + │   │   │   │   ├── User.java + │   │   │   │   ├── UserLookupAction.java + │   │   │   │   └── WarnCookieHandlerAction.java + │   │   │   └── util + │   │   │   ├── ProtocolParameterAuthority.java + │   │   │   ├── UriEncoder.java + │   │   │   └── UrlBuilder.java + + +Also, note that for any custom Java component to compile and be included in the final `cas.war` file, the `pom.xml` in the Maven overlay must include a reference to the Maven Java compiler so classes can compiled. Here is a *sample* build configuration: + + +{% highlight xml %} + +... + + + + + org.apache.maven.plugins + maven-war-plugin + 2.3 + + cas + + + org.jasig.cas + cas-server-webapp + + WEB-INF/cas.properties + WEB-INF/classes/log4j.xml + ... + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${java.source.version} + ${java.target.version} + + + + + cas + + +... + +{% endhighlight %} + + +*(1) The filesystem hierarchy visualization is generated by the `tree` program.* diff --git a/cas-server-documentation/installation/Memcached-Ticket-Registry.md b/cas-server-documentation/installation/Memcached-Ticket-Registry.md new file mode 100644 index 000000000000..196db6328500 --- /dev/null +++ b/cas-server-documentation/installation/Memcached-Ticket-Registry.md @@ -0,0 +1,144 @@ +--- +layout: default +title: CAS - Memcached Ticket Registry +--- + +# Memcached Ticket Registry +Memcached integration is enabled by including the following dependency in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-integration-memcached + ${cas.version} + +{% endhighlight %} +`MemCacheTicketRegistry` stores tickets in one or more [memcached](http://memcached.org/) instances. The +[spymemcached](https://code.google.com/p/spymemcached/) library used by this component presents memcached as a +key/value store that accepts `String` keys and Java `Object` values. +Memcached stores data in exactly one node among many in a distributed cache, thus avoiding the requirement to replicate +or otherwise share data between nodes. A deterministic function is used to locate the node, _N'_, on which to store +key _K_: + + N' = f(h(K), N1, N2, N3, ... Nm) + +where _h(K)_ is the hash of key _K_, _N1 ... Nm_ is the set of cache nodes, and _N'_ ∈ _N ... Nm_. + +The function is deterministic in that it consistently produces the same result for a given key and set of cache nodes. +Note that a change in the set of available cache nodes may produce a different target node on which to store the key. + + +## Configuration Considerations +There are three core configuration concerns with memcached: + +1. Hash Algorithm +2. Node locator strategy +3. Object serialization mechanism + + +### Hash Algorithm +The hash algorithm is used to transform a key value into a memcached storage key that uniquely identifies the +corresponding value. The choice of hashing algorithm has implications for failover behavior that is important +for HA deployments. The `FNV1_64_HASH` algorithm is recommended since it offers a nice balance of speed and low +collision rate; see the +[javadocs](https://github.com/couchbase/spymemcached/blob/2.8.1/src/main/java/net/spy/memcached/DefaultHashAlgorithm.java) +for alternatives. + + +### Node Locator +The node locator serves as the deterministic node selection function for the memcached client provided by the +underlying spymemcached library. There are two choices: + +1. [ARRAY_MOD](https://github.com/couchbase/spymemcached/blob/2.8.1/src/main/java/net/spy/memcached/ArrayModNodeLocator.java) +2. [CONSISTENT](https://github.com/couchbase/spymemcached/blob/2.9.0/src/main/java/net/spy/memcached/KetamaNodeLocator.java) + +The array modulus mechanism is the default and suitable for cases when the number of nodes in the memcached pool is +expected to be consistent. The algorithm simply computes an index into the array of memcached nodes: + + hash(key) % length(nodes) + +Obviously the selected index is a function of the number of memcached nodes, so variance in number of nodes produces +variance in the node selected to store the key, which is undesirable. + +The consistent strategy generally provides a target node that does not vary with the number of nodes. This strategy +should be used in cases where the memcached pool may grow or shrink dynamically, including due to frequent node +failure. + + +### Object Serialization +Memcached stores bytes of data, so CAS tickets must be serialized to a byte array prior to storage. CAS ships with +a custom serialization component `KryoTranscoder` based on the [Kryo](https://code.google.com/p/kryo/) serialization +framework. This component is recommended over the default Java serialization mechanism since it produces much more +compact data, which benefits both storage requirements and throughput. + + +## Component Configuration +The following configuration is a template for `ticketRegistry.xml` Spring configuration: +{% highlight xml %} + + + + + + + + + + + + +{% endhighlight %} + +`MemCacheTicketRegistry` properties reference: + +{% highlight properties %} +# It is common to run memcached on every CAS node +memcached.servers=cas-1.example.org:11211,cas-2.example.org:11211,cas-3.example.org:11211 +memcached.hashAlgorithm=FNV1_64_HASH +memcached.protocol=BINARY +memcached.locatorType=ARRAY_MOD +memcached.failureMode=Redistribute +{% endhighlight %} + +## High Availability Considerations +Memcached does not provide for replication by design, but the client is tolerant to node failures with +`failureMode="Redistribute"`. In this mode a write failure will simply cause the client to flag the node as failed +and remove it from the set of available nodes. It subsequently recomputes the node location function with the reduced +node set to find a new node on which to store the key. If the node location function selects the same node, +which is likely for the _CONSISTENT_ strategy, a backup node will be computed. The value is written to and read from +the failover node until the primary node recovers. The client will periodically check the failed node for liveliness +and restore it to the node pool as soon as it recovers. When the primary node is resurrected, if it contains a value +for a particular key, it would supercede the value known to the failover node. The most common effect on CAS behavior +in this circumstance would occur when ticket-granting tickets have duplicate values, which could affect single sign-out +and prevent access to services. In particular, services accessed and forced authentications that occur while the +failover service is active would be lost when the failed node recovers. In most cases this behavior is tolerable, +but it can be avoided by restarting the memcached service on the failed node prior to rejoining the cache pool. + +A read failure in _Redistribute_ mode causes the node to be removed from the set of available nodes, a failover node +is computed, and a value is read from that node. In most cases this results in a key not found situation. The effect +on CAS behavior depends on the type of ticket requested: + +* Service ticket - Service access would be denied for the requested ticket, but permitted for subsequent attempts since +a new ticket would be generated and validated. +* Ticket-granting ticket - The SSO session would be terminated and reauthentication would be required. + +Read failures are thus entirely innocuous for environments where reauthentication is acceptable. diff --git a/cas-server-documentation/installation/Monitoring-Statistics.md b/cas-server-documentation/installation/Monitoring-Statistics.md new file mode 100644 index 000000000000..76d682eac843 --- /dev/null +++ b/cas-server-documentation/installation/Monitoring-Statistics.md @@ -0,0 +1,235 @@ +--- +layout: default +title: CAS - Monitoring & Statistics +--- + +#Monitoring +The CAS server exposes a `/status` endpoint that may be used to inquire about the health and general state of the software. Access to the endpoint is secured by Spring Security at `src/main/webapp/WEB-INF/spring-configuration/securityContext.xml`: + +{% highlight xml %} + + + +{% endhighlight %} + +Access is granted the following settings in `cas.properties` file: + +{% highlight bash %} +# Spring Security's EL-based access rules for the /status URI of CAS that exposes health check information +cas.securityContext.status.access=hasIpAddress('127.0.0.1') + +{% endhighlight %} + + +##Sample Output + +{% highlight bash %} +Health: OK + + 1.MemoryMonitor: OK - 322.13MB free, 495.09MB total. +{% endhighlight %} + + +## Internal Configuration Report + +CAS also provides a `/status/config` endpoint that produces a report of the runtime CAS configuration, which includes all components that are under the `org.jasig` +package as well as settings defined in the `cas.properties` file. The output of this endpoint is a JSON representation of the runtime that is rendered into a modest visualization: + +![](https://cloud.githubusercontent.com/assets/1205228/7085296/35819ff0-df2a-11e4-9818-9119fd30588e.jpg) + +#Statistics +Furthermore, the CAS web application has the ability to present statistical data about the runtime environment as well as ticket registry's performance. + +The CAS server exposes a `/statistics` endpoint that may be used to inquire about the runtime state of the software. Access to the endpoint is secured by Spring Security at `src/main/webapp/WEB-INF/spring-configuration/securityContext.xml`: + +{% highlight xml %} + + + +{% endhighlight %} + +Access is granted the following settings in `cas.properties` file: + +{% highlight bash %} +# Spring Security's EL-based access rules for the /statistics URI of CAS that exposes stats about the CAS server +cas.securityContext.statistics.access=hasIpAddress('127.0.0.1') + +{% endhighlight %} + +![](http://i.imgur.com/8CXPgOC.png) + +##Performance Statistics +CAS also uses the [Dropwizard Metrics framework](https://dropwizard.github.io/metrics/), that provides set of utilities for calculating and displaying performance statistics. + +###Configuration +The metrics configuration is controlled via the `/src/main/webapp/WEB-INF/spring-configuration/metricsContext.xml` file. The configuration will output all performance-related data and metrics to the logging framework. The reporting interval can be configured via the `cas.properties` file: + +{% highlight bash %} + +# Define how often should metric data be reported. Default is 30 seconds. +# metrics.refresh.internal=30s + +{% endhighlight %} + +Various metrics can also be reported via JMX. Metrics are exposes via JMX MBeans. + +{% highlight xml %} + + + +{% endhighlight %} + +To explore this you can use VisualVM (which ships with most JDKs as jvisualvm) with the VisualVM-MBeans plugins installed or JConsole (which ships with most JDKs as jconsole): + +![](http://i.imgur.com/g8fmUlE.png) + +Additionally, various metrics on JVM performance and data are also reported. The metrics contain a number of reusable gauges and metric sets which allow you to easily instrument JVM internals. + +{% highlight xml %} + + + + + + + + +{% endhighlight %} + +Supported metrics include: + +- Run count and elapsed times for all supported garbage collectors +- Memory usage for all memory pools, including off-heap memory +- Breakdown of thread states, including deadlocks +- File descriptor usage +- ... + +###Loggers +All performance data and metrics are routed to a log file via the Log4j configuration: + +{% highlight xml %} +... + + + + + + + + + +... + + + + + +{% endhighlight %} + + +###Sample Output +{% highlight bash %} +type=GAUGE, name=jvm.gc.Copy.count, value=22 +type=GAUGE, name=jvm.gc.Copy.time, value=466 +type=GAUGE, name=jvm.gc.MarkSweepCompact.count, value=3 +type=GAUGE, name=jvm.gc.MarkSweepCompact.time, value=414 +type=GAUGE, name=jvm.memory.heap.committed, value=259653632 +type=GAUGE, name=jvm.memory.heap.init, value=268435456 +type=GAUGE, name=jvm.memory.heap.max, value=1062338560 +type=GAUGE, name=jvm.memory.heap.usage, value=0.09121857348376773 +type=GAUGE, name=jvm.memory.heap.used, value=96905008 + +type=METER, name=org.jasig.cas.CentralAuthenticationServiceImpl.CREATE_TICKET_GRANTING_TICKET_METER, count=0, mean_rate=0.0, m1=0.0, m5=0.0, m15=0.0, rate_unit=events/millisecond + +type=METER, name=org.jasig.cas.CentralAuthenticationServiceImpl.DESTROY_TICKET_GRANTING_TICKET_METER, count=0, mean_rate=0.0, m1=0.0, m5=0.0, m15=0.0, rate_unit=events/millisecond + +type=TIMER, name=org.jasig.cas.CentralAuthenticationServiceImpl.GRANT_SERVICE_TICKET_TIMER, count=0, min=0.0, max=0.0, mean=0.0, stddev=0.0, median=0.0, p75=0.0, p95=0.0, p98=0.0, p99=0.0, p999=0.0, mean_rate=0.0, m1=0.0, m5=0.0, m15=0.0, rate_unit=events/millisecond, duration_unit=milliseconds + +type=TIMER, name=org.jasig.cas.CentralAuthenticationServiceImpl.VALIDATE_SERVICE_TICKET_TIMER, count=0, min=0.0, max=0.0, mean=0.0, stddev=0.0, median=0.0, p75=0.0, p95=0.0, p98=0.0, p99=0.0, p999=0.0, mean_rate=0.0, m1=0.0, m5=0.0, m15=0.0, rate_unit=events/millisecond, duration_unit=milliseconds + +{% endhighlight %} + +###Viewing Metrics on the Web +The CAS web application exposes a `/statistics` endpoint that can be used to view metrics and stats in the browser. The endpoint is protected by Spring Security, and the access rules are placed inside the `cas.properties` file: + +{% highlight bash %} +# Spring Security's EL-based access rules for the /statistics URI of CAS that exposes stats about the CAS server +cas.securityContext.statistics.access=hasIpAddress('127.0.0.1') +{% endhighlight %} + +Once access is granted, the following sub-endpoints can be used to query the CAS server's status and metrics: + +####`/statistics/ping` +Reports back `pong` to indicate that the CAS server is running. + +####`/statistics/metrics?pretty=true` +Reports back metrics and performance data. The optional `pretty` flag attempts to format the JSON output. + +####`/statistics/threads` +Reports back JVM thread info. + +####`/statistics/healthcheck` +Unused at this point, but may be used later to output health examinations of the CAS server's internals, such as ticket registry, etc. + +##Routing logs to SysLog +CAS logging framework does have the ability to route messages to an external syslog instance. To configure this, you first to configure the `SysLogAppender` and then specify which messages needs to be routed over to this instance: + +{% highlight xml %} +... + + + + +... + + + + + + + +{% endhighlight %} + +You can also configure the remote destination output over SSL and specify the related keystore configuration: + +{% highlight xml %} +... + + + + + + + + + + +... + +{% endhighlight %} + +For additional logging functionality, please refer to the Log4j configuration url or view +the [CAS Logging functionality](Logging.html). + +### SSO Sessions Report + +CAS also provides a `/statistics/ssosessions` endpoint that produces a report of all active non-expired SSO sessions. The output of this endpoint is a JSON representation of SSO sessions that is rendered into a modest visualization: + +![](https://cloud.githubusercontent.com/assets/1205228/6801195/fcf77186-d1e2-11e4-8059-cfa1d7e80d83.PNG) + +By default, ticket-granting ticket ids are not shown. This behavior can be controlled via `cas.properties`: + +{% highlight properties %} + +## +# Reports +# +# Setting to whether include the ticket granting ticket id in the report +# sso.sessions.include.tgt=false + +{% endhighlight %} + diff --git a/cas-server-documentation/installation/OAuth-OpenId-Authentication.md b/cas-server-documentation/installation/OAuth-OpenId-Authentication.md new file mode 100644 index 000000000000..6690b6d8e600 --- /dev/null +++ b/cas-server-documentation/installation/OAuth-OpenId-Authentication.md @@ -0,0 +1,109 @@ +--- +layout: default +title: CAS - OAuth Authentication +--- + +# OAuth/OpenID Authentication + +
CAS as OAuth Server

This page specifically describes how to enable OAuth/OpenID server support for CAS. If you would like to have CAS act as an OAuth/OpenID client communicating with other providers (such as Google, Facebook, etc), see this page.

+ +To get a better understanding of the OAuth/OpenID protocol support in CAS, [see this page](../protocol/OAuth-Protocol.html). + +## Configuration +Support is enabled by including the following dependency in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-support-oauth + ${cas.version} + +{% endhighlight %} + +#Configuration + + +##Add the OAuth20WrapperController + +To add the `OAuth20WrapperController`, you need to add the mapping between the /oauth2.0/* url and the CAS servlet in the *web.xml* file: + +{% highlight xml %} + + cas + /oauth2.0/* + +{% endhighlight %} + +You have to create the controller itself in the *cas-servlet.xml* file: + +{% highlight xml %} + +{% endhighlight %} + +The *loginUrl* is the login url of the CAS server. The timeout is the lifetime of a CAS ticket granting ticket (in seconds, not in milliseconds!) with its mapping in the `handlerMappingC` bean (*cas-servlet.xml* file): + +{% highlight xml %} + + + + serviceValidateController + + ... + + statisticsController + oauth20WrapperController + + + + +{% endhighlight %} + + +##Add the needed CAS services + +###Callback Authorization + +One service is needed to make the OAuth wrapper works in CAS. It defines the callback url after CAS authentication to return to the OAuth wrapper as a CAS service. +**Note**: the callback url must end with "callbackAuthorize". + +{% highlight xml %} + + + + + + +... +{% endhighlight %} + + +###OAuth Clients + +Every OAuth client must be defined as a CAS service (notice the new *clientId* and *clientSecret* properties, specific to OAuth): + +{% highlight xml %} + + + + + +... +{% endhighlight %} + diff --git a/cas-server-documentation/installation/RADIUS-Authentication.md b/cas-server-documentation/installation/RADIUS-Authentication.md new file mode 100644 index 000000000000..b888da5e328a --- /dev/null +++ b/cas-server-documentation/installation/RADIUS-Authentication.md @@ -0,0 +1,58 @@ +--- +layout: default +title: CAS - RADIUS Authentication +--- + +# RADIUS Authentication +RADIUS support is enabled by including the following dependency in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-support-radius + ${cas.version} + +{% endhighlight %} + +## RADIUS Components + +######`RadiusAuthenticationHandler` +The RADIUS handler accepts username/password credentials and delegates authentication to one or more RADIUS +servers. It supports two types of failovers: failover on an authentication failure, and failover on a server exception. + +* `failoverOnAuthenticationFailure` - True to continue to the next configured RADIUS server on authentication failure, +false otherwise. This flag is typically set when user accounts are spread across one or more RADIUS servers. +* `failoverOnException` - True to continue to next configured RADIUS server on an error other than authentication +failure, false otherwise. This flag is typically set to support highly available deployments where authentication +should proceed in the face of one or more RADIUS server failures. +* `servers` - Array of RADIUS servers to delegate to for authentication. + + +######`JRadiusServerImpl` +Component representing a RADIUS server has the following configuration properties. + +* `protocol` - radius protocol to use. +* `clientFactory` - factory establish and create radius client instances. + + +## RADIUS Configuration Example +{% highlight xml %} + + + + + + + + + + + +{% endhighlight %} diff --git a/cas-server-documentation/installation/Remote-Address-Authentication.md b/cas-server-documentation/installation/Remote-Address-Authentication.md new file mode 100644 index 000000000000..63044db2639f --- /dev/null +++ b/cas-server-documentation/installation/Remote-Address-Authentication.md @@ -0,0 +1,61 @@ +--- +layout: default +title: CAS - Remote Address Authentication +--- + +# Remote Address Authentication +This handler uses the request's remote address to transparently authenticate a user, having verified the address against a range of configured IP addresses. The mechanics of this approach are very similar to X.509 certificate authentication, but trust is instead placed on the client internal network address. + +The benefit of this approach is that transparent authentication is achieved within a large corporate network without the need to manage certificates. + +
Be Careful

Keep in mind that this authentication mechanism should only be enabled for internal network clients with relatively static IP addresses.

+ + +## Caveats + +This method of authentication assumes internal clients will be hitting the CAS server directly and not coming via a web proxy. In the event of the client using the web proxy the likelihood of the remote address lookup succeeding is reduced because to CAS the client address is that of the proxy server and not the client. Given that this form of CAS authentication would typically be deployed within an internal network this is generally not a problem. + + +## Authentication Components +Support is enabled by including the following dependency in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-support-generic + ${cas.version} + +{% endhighlight %} + +### Configuring Authentication +{% highlight xml %} + + + +{% endhighlight %} + +This authentication handler checks the inbound client IP address to see if it falls within the internal network. The `RemoteAddressAuthenticationHandler` bean has one property: + +- `ipNetworkRange` - This defines the internal network parameters in the form of a subnet and netmask. e.g. `192.168.1.0/255.255.255.0` or `10.0.0.0/255.255.0.0`. + +Also declare the following bean, which extracts the client's IP address from the request. + +{% highlight xml %} + + + +{% endhighlight %} + +### Configuring Webflow + +Add the following action to the webflow configuration file: + +{% highlight xml %} + + + + + +{% endhighlight %} + +You should appropriately evaluate your configuratioon to route the flow to `startAuthenticate` where needed. diff --git a/cas-server-documentation/installation/SPNEGO-Authentication.md b/cas-server-documentation/installation/SPNEGO-Authentication.md new file mode 100644 index 000000000000..20bff91d83f9 --- /dev/null +++ b/cas-server-documentation/installation/SPNEGO-Authentication.md @@ -0,0 +1,269 @@ +--- +layout: default +title: CAS - SPNEGO Authentication +--- + +# SPNEGO Authentication +[SPNEGO](http://en.wikipedia.org/wiki/SPNEGO) is an authentication technology that is primarily used to provide +transparent CAS authentication to browsers running on Windows running under Active Directory domain credentials. +There are three actors involved: the client, the CAS server, and the Active Directory Domain Controller/KDC. + + 1. Client sends CAS: HTTP GET to CAS for cas protected page + 2. CAS responds: HTTP 401 - Access Denied WWW-Authenticate: Negotiate + 3. Client sends ticket request: Kerberos(KRB_TGS_REQ) Requesting ticket for HTTP/cas.example.com@REALM + 4. Kerberos KDC responds: Kerberos(KRB_TGS_REP) Granting ticket for HTTP/cas.example.com@REALM + 5. Client sends CAS: HTTP GET Authorization: Negotiate w/SPNEGO Token + 6. CAS responds: HTTP 200 - OK WWW-Authenticate w/SPNEGO response + requested page. + +The above interaction occurs only for the first request, when there is no CAS ticket-granting ticket associated with +the user session. Once CAS grants a ticket-granting ticket, the SPNEGO process will not happen again until the CAS +ticket expires. + + +## SPNEGO Requirements +* Client is logged in to a Windows Active Directory domain. +* Supported browser and JDK. +* CAS is running MIT kerberos against the AD domain controller. + + +## SPNEGO Components +SPNEGO support is enabled by including the following dependency in the Maven WAR overlay: + + + org.jasig.cas + cas-server-support-spnego + ${cas.version} + + + +######`JCIFSSpnegoAuthenticationHandler` +The authentication handler that provides SPNEGO support in both Kerberos and NTLM flavors. NTLM is disabled by default. +Configuration properties: + +* `principalWithDomainName` - True to include the domain name in the CAS principal ID, false otherwise. +* `NTLMallowed` - True to enable NTLM support, false otherwise. (Disabled by default.) + + +######`JCIFSConfig` +Configuration helper for JCIFS and the Spring framework. Configuration properties: + +* `jcifsServicePrincipal` - service principal name. +* `jcifsServicePassword` - service principal password. +* `kerberosDebug` - True to enable kerberos debugging, false otherwise. +* `kerberosRealm` - Kerberos realm name. +* `kerberosKdc` - Kerberos KDC address. +* `loginConf` - Path to the login.conf JAAS configuration file. + + + +######`SpnegoNegociateCredentialsAction` +CAS login Webflow action that begins the SPNEGO authenticaiton process. The action checks the `Authorization` request +header for a suitable value (`Negotiate` for Kerberos or `NTLM`). If the check is successful, flow continues to the +`SpnegoCredentialsAction` state; otherwise a 401 (not authorized) response is returned. + + +######`SpnegoCredentialsAction` +Constructs CAS credentials from the encoded GSSAPI data in the `Authorization` request header. The standard CAS +authentication process proceeds as usual after this step: authentication is attempted with a suitable handler, +`JCIFSSpnegoAuthenticationHandler` in this case. The action also sets response headers accordingly based on whether +authentication succeeded or failed. + + +## SPNEGO Configuration + + +### Create SPN Account +Create an Active Directory account for the Service Principal Name (SPN) and record the username and password, which +will be used subsequently to configure the `JCIFSConfig` component. + + +### Create Keytab File +The keytab file enables a trust link between the CAS server and the Key Distribution Center (KDC); an Active Directory +domain controller serves the role of KDC in this context. +The [`ktpass` tool](http://technet.microsoft.com/en-us/library/cc753771.aspx) is used to generate the keytab file, +which contains a cryptographic key. Be sure to execute the command from an Active Directory domain controller as +administrator. + + +### Test SPN Account +Install and configure MIT Kerberos V on the CAS server host(s). The following sample `krb5.conf` file may be used +as a reference. + + [logging] + default = FILE:/var/log/krb5libs.log + kdc = FILE:/var/log/krb5kdc.log + admin_server = FILE:/var/log/kadmind.log + + [libdefaults] + ticket_lifetime = 24000 + default_realm = YOUR.REALM.HERE + default_keytab_name = /home/cas/kerberos/myspnaccount.keytab + dns_lookup_realm = false + dns_lookup_kdc = false + default_tkt_enctypes = rc4-hmac + default_tgs_enctypes = rc4-hmac + + [realms] + YOUR.REALM.HERE = { + kdc = your.kdc.your.realm.here:88 + } + + [domain_realm] + .your.realm.here = YOUR.REALM.HERE + your.realm.here = YOUR.REALM.HERE + +Then verify that your are able to read the keytab file: + + klist -k + +Then verify that your are able to read the keytab file: + + kinit a_user_in_the_realm@YOUR.REALM.HERE + klist + + +### Browser Configuration +* Internet Explorer - Enable _Integrated Windows Authentication_ and add the CAS server URL to the _Local Intranet_ +zone. +* Firefox - Set the `network.negotiate-auth.trusted-uris` configuration parameter in `about:config` to the CAS server +URL, e.g. https://cas.example.com. + + +### CAS Component Configuration +Define two new action states in `login-webflow.xml` before the `viewLoginForm` state: + +{% highlight xml %} + + + + + + + + + + +{% endhighlight %} + +Additionally, find action `generateLoginTicket` - replace `viewLoginForm` with `startAuthenticate`. + +Add two bean definitions in `cas-servlet.xml`: + +{% highlight xml %} + + + +{% endhighlight %} + +Update `deployerConfigContext.xml` according to the following template: + +{% highlight xml %} + + + + + + + + + + + + + + + + + + + + +{% endhighlight %} + +Provide a JAAS `login.conf` file in a location that agrees with the `loginConf` property of the `JCIFSConfig` bean +above. + + jcifs.spnego.initiate { + com.sun.security.auth.module.Krb5LoginModule required storeKey=true useKeyTab=true keyTab="/home/cas/kerberos/myspnaccount.keytab"; + }; + jcifs.spnego.accept { + com.sun.security.auth.module.Krb5LoginModule required storeKey=true useKeyTab=true keyTab="/home/cas/kerberos/myspnaccount.keytab"; + }; + +## Client Selection Strategy +CAS provides a set of components that attempt to activate the SPNEGO flow conditionally, in case deployers need a configurable way to decide whether SPNEGO should be applied to the current authentication/browser request. The components provided are webflow actions that return either `yes` or `no` to the webflow and allow you to reroute the webflow conditionally based the outcome, to either SPNEGO or the normal CAS login flow. + +The activation strategies are as follows: + +### By Remote IP +Checks to see if the request's remote ip address matches a predefine pattern. + +```xml + +``` + +### By Hostname +Checks to see if the request's remote hostname matches a predefine pattern. This action supports all functionality provided by `BaseSpnegoKnownClientSystemsFilterAction`. + +```xml + +``` + +### By LDAP Attribute +Checks an LDAP instance for the remote hostname, to locate a pre-defined attribute whose mere existence would allow the webflow to resume to SPNEGO. This action supports all functionality provided by `BaseSpnegoKnownClientSystemsFilterAction`. + + +```xml + +``` + +Sample search request and filer: + +```xml + + + + + + + +``` + +### Webflow Configuration + +Insert the appropriate action before SPNEGO initiation, assigning a `yes` response route to SPNEGO, and a `no` response to route to viewing the login form. + +```xml + + + + + + +... +``` diff --git a/cas-server-documentation/installation/Service-Management.md b/cas-server-documentation/installation/Service-Management.md new file mode 100644 index 000000000000..bf6a1ae2580d --- /dev/null +++ b/cas-server-documentation/installation/Service-Management.md @@ -0,0 +1,525 @@ +--- +layout: default +title: CAS - Service Management +--- + +# Service Management +The CAS service management facility allows CAS server administrators to declare and configure which services +(CAS clients) may make use of CAS in which ways. The core component of the service management facility is the +service registry, provided by the `ServiceRegistryDao` component, that stores one or more registered services +containing metadata that drives a number of CAS behaviors: + +* Authorized services - Control which services may participate in a CAS SSO session. +* Forced authentication - Provides administrative control for forced authentication. +* Attribute release - Provide user details to services for authorization and personalization. +* Proxy control - Further restrict authorized services by granting/denying proxy authentication capability. +* Theme control - Define alternate CAS themese to be used for particular services. + +The service management webapp is a Web application that may be deployed along side CAS that provides a GUI +to manage service registry data. + + +## Demo +The Service Management web application is available for demo at [https://jasigcasmgmt.herokuapp.com](https://jasigcasmgmt.herokuapp.com) + + +## Considerations +It is not required to use the service management facility explicitly. CAS ships with a default configuration that is +suitable for deployments that do not need or want to leverage the capabilities above. The default configuration allows +any service contacting CAS over https/imaps to use CAS and receive any attribute configured by an `IPersonAttributeDao` +bean. + + +A premier consideration around service management is whether to leverage the user interface. If the service management +webapp is used, then a `ServiceRegistryDao` that provides durable storage (e.g. `JpaServiceRegistryDaoImpl`) must be +used to preserve state across restarts. + +It is perfectly acceptable to avoid the service management webapp Web application for managing registered service data. +In fact, configuration-driven methods (e.g. XML, JSON) may be preferable in environments where strict configuration +management controls are required. + + +## Registered Services + +Registered services present the following metadata: + +| Field | Description +|-----------------------------------+--------------------------------------------------------------------------------+ +| `id` | Required unique identifier. In most cases this is managed automatically by the `ServiceRegistryDao`. +| `name` | Required name (255 characters or less). +| `description` | Optional free-text description of the service. (255 characters or less) +| `logo` | Optional path to an image file that is the logo for this service. The image will be displayed on the login page along with the service description and name. +| `serviceId` | Required [Ant pattern](http://ant.apache.org/manual/dirtasks.html#patterns) or [regular expression](http://docs.oracle.com/javase/tutorial/essential/regex/) describing a logical service. A logical service defines one or more URLs where a service or services are located. The definition of the url pattern must be **done carefully** because it can open security breaches. For example, using Ant pattern, if you define the following service : `http://example.*/myService` to match `http://example.com/myService` and `http://example.fr/myService`, it's a bad idea as it can be tricked by `http://example.hostattacker.com/myService`. The best way to proceed is to define the more precise url patterns. +| `theme` | Optional [Spring theme](http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/mvc.html#mvc-themeresolver) that may be used to customize the CAS UI when the service requests a ticket. See [this guide](User-Interface-Customization.html) for more details. +| `proxyPolicy` | Determines whether the service is able to proxy authentication, not whether the service accepts proxy authentication. +| `evaluationOrder` | Required value that determines relative order of evaluation of registered services. This flag is particularly important in cases where two service URL expressions cover the same services; evaluation order determines which registration is evaluated first. +| `requiredHandlers` | Set of authentication handler names that must successfully authenticate credentials in order to access the service. +| `attributeReleasePolicy` | The policy that describes the set of attributes allows to be released to the application, as well as any other filtering logic needed to weed some out. See [this guide](../integration/Attribute-Release.html) for more details on attribute release and filters. +| `logoutType` | Defines how this service should be treated once the logout protocol is initiated. Acceptable values are `LogoutType.BACK_CHANNEL`, `LogoutType.FRONT_CHANNEL` or `LogoutType.NONE`. See [this guide](Logout-Single-Signout.html) for more details on logout. +| `usernameAttributeProvider` | The provider configuration which dictates what value as the "username" should be sent back to the application. See [this guide](../integration/Attribute-Release.html) for more details on attribute release and filters. +| `accessStrategy` | The strategy configuration that outlines and access rules for this service. It describes whether the service is allowed, authorized to participate in SSO, or can be granted access from the CAS perspective based on a particular attribute-defined role, aka RBAC. See [this guide](../integration/Attribute-Release.html) for more details on attribute release and filters. +| `publicKey` | The public key associated with this service that is used to authorize the request by encrypting certain elements and attributes in the CAS validation protocol response, such as [the PGT](Configure-Proxy-Authentication.html) or [the credential](../integration/ClearPass.html). See [this guide](../integration/Attribute-Release.html) for more details on attribute release and filters. +| `logoutUrl` | URL endpoint for this service to receive logout requests. See [this guide](Logout-Single-Signout.html) for more details + +###Configure Service Access Strategy +The access strategy of a registered service provides fine-grained control over the service authorization rules. it describes whether the service is allowed to use the CAS server, allowed to participate in single sign-on authentication, etc. Additionally, it may be configured to require a certain set of principal attributes that must exist before access can be granted to the service. This behavior allows one to configure various attributes in terms of access roles for the application and define rules that would be enacted and validated when an authentication request from the application arrives. + +####Components + +#####`RegisteredServiceAccessStrategy` +This is the parent interface that outlines the required operations from the CAS perspective that need to be carried out in order to determine whether the service can proceed to the next step in the authentication flow. + +#####`DefaultRegisteredServiceAccessStrategy` +The default access manager allows one to configure a service with the following properties: + +| Field | Description +|-----------------------------------+--------------------------------------------------------------------------------+ +| `enabled` | Flag to toggle whether the entry is active; a disabled entry produces behavior equivalent to a non-existent entry. +| `ssoEnabled` | Set to `false` to force users to authenticate to the service regardless of protocol flags (e.g. `renew=true`). This flag provides some support for centralized application of security policy. +| `requiredAttributes` | A `Map` of required principal attribute names along with the set of values for each attribute. These attributes must be available to the authenticated Principal and resolved before CAS can proceed, providing an option for role-based access control from the CAS perspective. If no required attributes are presented, the check will be entirely ignored. +| `requireAllAttributes` | Flag to toggle to control the behavior of required attributes. Default is `true`, which means all required attribute names must be present. Otherwise, at least one matching attribute name may suffice. Note that this flag only controls which and how many of the attribute **names** must be present. If attribute names satisfy the CAS configuration, at the next step at least one matching attribute value is required for the access strategy to proceed successfully. + +
Are we sensitive to case?

Note that comparison of principal/required attributes is case-sensitive. Exact matches are required for any individual attribute value.

+ +
Released Attributes

Note that if the CAS server is configured to cache attributes upon release, all required attributes must also be released to the relying party. See this guide for more info on attribute release and filters.

+ +####Configuration of Role-based Access Control +Some examples of RBAC configuration follow: + +* Service is not allowed to use CAS: + +{% highlight xml %} + + + + +... + +{% endhighlight %} + + +* Service will be challenged to present credentials every time, thereby not using SSO: + +{% highlight xml %} + + + + +... + +{% endhighlight %} + + +* To access the service, the principal must have a `cn` attribute with the value of `admin` **AND** a +`givenName` attribute with the value of `Administrator`: + +{% highlight xml %} + + + + + + + + + +... + +{% endhighlight %} + +* To access the service, the principal must have a `cn` attribute whose value is either of `admin`, `Admin` or `TheAdmin`. + +{% highlight xml %} + + + + + + + admin + Admin + TheAdmin + + + + + +... + +{% endhighlight %} + + +* To access the service, the principal must have a `cn` attribute whose value is either of `admin`, `Admin` or `TheAdmin`, +OR the principal must have a `member` attribute whose value is either of `admins`, `adminGroup` or `staff`. + +{% highlight xml %} + + + + + + + admin + Admin + TheAdmin + + + + + admins + adminGroup + staff + + + + + +... + +{% endhighlight %} + + +###Configure Proxy Authentication Policy +Each registered application in the registry may be assigned a proxy policy to determine whether the service is allowed for proxy authentication. This means that a PGT will not be issued to a service unless the proxy policy is configured to allow it. Additionally, the policy could also define which endpoint urls are in fact allowed to receive the PGT. + +Note that by default, the proxy authentication is disallowed for all applications. + +####Components + +#####`RefuseRegisteredServiceProxyPolicy` +Disallows proxy authentication for a service. This is default policy and need not be configured explicitly. + +#####`RegexMatchingRegisteredServiceProxyPolicy` +A proxy policy that only allows proxying to PGT urls that match the specified regex pattern. + +{% highlight xml %} + + + + + +{% endhighlight %} + +## Persisting Registered Service Data + +######`InMemoryServiceRegistryDaoImpl` +CAS uses in-memory services management by default, with the registry seeded from registration beans wired via Spring. + +{% highlight xml %} + + + + + + +{% endhighlight %} + +This component is _NOT_ suitable for use with the service management webapp since it does not persist data. +On the other hand, it is perfectly acceptable for deployments where the XML configuration is authoritative for +service registry data and the UI will not be used. + +######`JsonServiceRegistryDao` +This DAO reads services definitions from JSON configuration files at the application context initialization time. JSON files are +expected to be found inside a configured directory location and this DAO will recursively look through the directory structure to find relevant JSON files. + +{% highlight xml %} + +{% endhighlight %} + +A sample JSON file follows: + +{% highlight json %} +{ + "@class" : "org.jasig.cas.services.RegexRegisteredService", + "id" : 103935657744185, + "description" : "Service description", + "serviceId" : "https://**", + "name" : "testJsonFile", + "theme" : "testtheme", + "proxyPolicy" : { + "@class" : "org.jasig.cas.services.RegexMatchingRegisteredServiceProxyPolicy", + "pattern" : "https://.+" + }, + "accessStrategy" : { + "@class" : "org.jasig.cas.services.DefaultRegisteredServiceAccessStrategy" + }, + "evaluationOrder" : 1000, + "usernameAttributeProvider" : { + "@class" : "org.jasig.cas.services.DefaultRegisteredServiceUsernameProvider" + }, + "logoutType" : "BACK_CHANNEL", + "requiredHandlers" : [ "java.util.HashSet", [ "handler1", "handler2" ] ], + "attributeReleasePolicy" : { + "@class" : "org.jasig.cas.services.ReturnAllowedAttributeReleasePolicy", + "attributeFilter" : { + "@class" : "org.jasig.cas.services.support.RegisteredServiceRegexAttributeFilter", + "pattern" : "\\w+" + }, + "allowedAttributes" : [ "java.util.ArrayList", [ "uid", "cn", "sn" ] ] + } +} + +{% endhighlight %} + + +
Clustering Services

+You MUST consider that if your CAS server deployment is clustered, each CAS node in the cluster must have +access to the same set of JSON configuration files as the other, or you may have to devise a strategy to keep +changes synchronized from one node to the next. +

+ +The JSON service registry is also able to auto detect changes to the specified directory. It will monitor changes to recognize +file additions, removals and updates and will auto-refresh CAS so changes do happen instantly. + +The naming convention for new JSON files is recommended to be the following: + + +{% highlight bash %} + +JSON fileName = serviceName + "-" + serviceNumericId + ".json" + +{% endhighlight %} + + +Based on the above formula, for example the above JSON snippet shall be named: `testJsonFile-103935657744185.json` + +
Duplicate Services

+As you add more files to the directory, you need to be absolutely sure that no two service definitions +will have the same id. If this happens, loading one definition will stop loading the other. While service ids +can be chosen arbitrarily, make sure all service numeric identifiers are unique. CAS will also output warnings +if duplicate data is found. +

+ +######`LdapServiceRegistryDao` +Service registry implementation which stores the services in a LDAP Directory. Uses an instance of `LdapRegisteredServiceMapper`, that by default is `DefaultLdapRegisteredServiceMapper` in order to configure settings for retrieval, search and persistence of service definitions. By default, entries are assigned the `objectclass` `casRegisteredService` attribute and are looked up by the `uid` attribute. + +{% highlight xml %} + + + + + + +{% endhighlight %} + +Note that the configuration of the mapper is optional and need not explicitly exist. + +

+ +######`DefaultLdapRegisteredServiceMapper` +The default mapper has support for the following optional items: + +| Field | Default Value +|-----------------------------------+--------------------------------------------------+ +| `objectClass` | casRegisteredService +| `serviceDefinitionAttribute` | description +| `idAttribute` | uid + +Service definitions are by default stored inside the `serviceDefinitionAttribute` attribute as JSON objects. The format and syntax of the JSON is identical to that of `JsonServiceRegistryDao`. + + +######`JpaServiceRegistryDaoImpl` +Stores registered service data in a database; the preferred choice when using the service management webapp. +The following schema shall be generated by CAS automatically for brand new deployments, and must be massaged +when doing CAS upgrades: + +{% highlight sql %} + +create table RegisteredServiceImpl ( + expression_type VARCHAR(15) DEFAULT 'ant' not null, + id bigint generated by default as identity (start with 1), + access_strategy blob(255), + attribute_release blob(255), + description varchar(255) not null, + evaluation_order integer not null, + logo varchar(255), + logout_type integer, + logout_url varchar(255), + name varchar(255) not null, + proxy_policy blob(255), + required_handlers blob(255), + public_key blob(255), + serviceId varchar(255) not null, + theme varchar(255), + username_attr blob(255), + primary key (id) +) + +{% endhighlight %} + + +The following configuration template may be applied to `deployerConfigContext.xml` to provide for persistent +registered service storage. The configuration assumes a `dataSource` bean is defined in the context. + +{% highlight xml %} + + + + + + ${database.hibernate.dialect} + update + ${database.hibernate.batchSize:10} + + + + + + org.jasig.cas.services + org.jasig.cas.ticket + org.jasig.cas.adaptors.jdbc + + + + + + + + + + + + + +{% endhighlight %} + +If you prefer a direct connection to the database, here's a sample configuration of the `dataSource`: + +{% highlight xml %} + +{% endhighlight %} + +The data source will need to be modified for your particular database (i.e. Oracle, MySQL, etc.), but the name `dataSource` should be preserved. Here is a MYSQL sample: + +{% highlight xml %} + +{% endhighlight %} + +You will also need to change the property `hibernate.dialect` in adequacy with your database in `cas.properties` and `deployerConfigContext.xml`. + +For example, for MYSQL the setting would be: + +In `cas.properties`: + +{% highlight bash %} +database.hibernate.dialect=org.hibernate.dialect.MySQLDialect +{% endhighlight %} + +In `deployerConfigContext.xml`: + +{% highlight xml %} +${database.hibernate.dialect} +{% endhighlight %} + +You will also need to ensure that the xml configuration file contains the `tx` namespace: + +{% highlight xml %} + +{% endhighlight %} + +Finally, when adding a new source new dependencies may be required on Hibernate, `commons-dbcp2`. Be sure to add those to your `pom.xml`. Below is a sample configuration for MYSQL. Be sure to adjust the version elements for the appropriate version number. + +{% highlight xml %} + + + org.jasig.cas + cas-server-support-jdbc + ${cas.version} + runtime + + + + org.apache.commons + commons-dbcp2 + ${commons.dbcp.version} + runtime + + + + org.hibernate + hibernate-core + ${hibernate.version} + compile + + + + org.hibernate + hibernate-entitymanager + ${hibernate.entitymgmr.version} + + + + mysql + mysql-connector-java + ${mysql.connector.version} + + +{% endhighlight %} + +## Service Management Webapp +The Services Management web application is a standardalone application that helps one manage service registrations and entries via a customizable user interface. The management web application *MUST* share the same registry configuration as the CAS server itself so the entire system can load the same services data. To learn more about the management webapp, +[please see this guide](Installing-ServicesMgmt-Webapp.html). diff --git a/cas-server-documentation/installation/Troubleshooting-Guide.md b/cas-server-documentation/installation/Troubleshooting-Guide.md new file mode 100644 index 000000000000..e98daf8846c2 --- /dev/null +++ b/cas-server-documentation/installation/Troubleshooting-Guide.md @@ -0,0 +1,175 @@ +--- +layout: default +title: CAS - Troubleshooting Guide +--- + +#Troubleshooting Guide + +##Authentication + +###Login Form Clearing Credentials on Submission +You may encounter an issue where upon submission of credentials on the login form, the screen clears the input data and asks the user again to repopulate the form. The CAS Server log may also indicate the received login ticket is invalid and therefore unable to accept the authentication request. + +The CAS server itself initiates a web session upon authentication requests that by default is configured to last for 5 minutes. The state of the session is managed at server-side and thus, once the session expires any authentication activity on the client side such as submission of the login form is disregarded until a new session a regenerated. Another possible cause may be related to a clustered CAS environment where CAS nodes are protected by a load balancer whose "timeout" is configured less than the default session expiration timeout of CAS. Thus, once the authentication form is submitted and the request is routed to the load balancer, it is seen as a new request and is redirected back to a CAS node for authentication. + +To remedy the problem, the suggestion is to configure the default CAS session timeout to be an appropriate value that matches the time a given user is expected to stay on the login screen. Similarly, the same change may be applied to the load balancer to treat authentication requests within a longer time span. In `web.xml`, adjust the `session-timeout` attribute to extend the session expiration time. + +###Application Not Authorized to Use CAS +You may encounter this error, when the requesting application/service url cannot be found in your CAS service registry. When an authentication request is submitted to the CAS `login` endpoint, the destination application is indicated as a url parameter which will be checked against the CAS service registry to determine if the application is allowed to use CAS. If the url is not found, this message will be displayed back. Since service definitions in the registry have the ability to be defined by a url pattern, it is entirely possible that the pattern in the registry for the service definition is misconfigured and does not produce a successful match for the requested application url. + +Please [review this guide](Service-Management.html) to better understand the CAS service registry. + +###Invalid/Expired CAS Tickets +You may experience `INVAILD_TICKET` related errors when attempting to use a CAS ticet whose expiration policy dictates that the ticket has expired. The CAS log should further explain in more detail if the ticket is considered expired, but for diagnostic purposes, you may want to adjust the [ticket expiration policy configuration](Configuring-Ticket-Expiration-Policy.html) to remove and troubleshoot this error. + +Furthermore, if the ticket itself cannot be located in the CAS ticket registry the ticket is also considered invalid. You will need to observe the ticket used and compare it with the value that exists in the ticket registry to ensure that the ticket id provided is valid. + +###Out of Heap Memory Error +{% highlight bash %} +java.lang.OutOfMemoryError: GC overhead limit exceeded + at java.util.Arrays.copyOfRange(Arrays.java:3658) + at java.lang.StringBuffer.toString(StringBuffer.java:671) + at +{% endhighlight %} + +You may encounter this error, when in all likelihood, a cache-based ticket registry such as EhCache is used whose eviction policy is not correctly configured. Objects and tickets are cached inside the registry storage back-end tend to linger around longer than they should or the eviction policy is not doing a good enough job to clean unused tickets that may be marked as expired by CAS. + +To troubleshoot, you can configure the JVM to perform a heap dump prior to exiting, which you should set up immediately so you have some additional information if/when it happens next time. The follow system properties should do the trick: + +{% highlight bash %} +-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath="/path/to/jvm-dump.hprof" +{% endhighlight %} + +Also ensure that your container is configured to have enough memory available. For Apache Tomcat, the following setting as an environment variable may be configured: + +{% highlight bash %} +CATALINA_OPTS=-Xms1000m -Xmx2000m +{% endhighlight %} + +You will want to profile your server with something like [JVisualVM](http://visualvm.java.net/) which should be [bundled with the JDK](https://docs.oracle.com/javase/7/docs/technotes/tools/share/jvisualvm.html). This will help you see what is actually going on with your memory. + +You might also consider taking periodic heap dumps using the JMap tool or [YourKit Java profiler](http://www.yourkit.com/java/profiler/) and analyzing offline using some analysis tool. + +Finally, review the eviction policy of your ticket registry and ensure the values that determine object lifetime are appropriate for your environment. + +##SSL + +###PKIX Path Building Failed + +{% highlight bash %} + +Sep 28, 2009 4:13:26 PM org.jasig.cas.client.validation.AbstractCasProtocolUrlBasedTicketValidator retrieveResponseFromServer +SEVERE: javax.net.ssl.SSLHandshakeException: +sun.security.validator.ValidatorException: PKIX path building failed: +sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target +javax.net.ssl.SSLHandshakeException: +sun.security.validator.ValidatorException: PKIX path building failed: +sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target + at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Unknown Source) + at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(Unknown Source) + at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Unknown Source) + at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Unknown Source) + at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(Unknown Source) + +{% endhighlight %} + +PKIX path building errors are the most common SSL errors. The problem here is that the CAS client does not trust the certificate presented by the CAS server; most often this occurs because of using a *self-signed certificate* on the CAS server. To resolve this error, import the CAS server certificate into the system truststore of the CAS client. If the certificate is issued by your own PKI, it is better to import the root certificate of your PKI into the CAS client truststore. + +By default the Java system truststore is at `$JAVA_HOME/jre/lib/security/cacerts`. The certificate to be imported **MUST** be a DER-encoded file. If the contents of the certificate file are binary, it's likely DER-encoded; if the file begins with the text `---BEGIN CERTIFICATE---`, it is PEM-encoded and needs to be converted to DER encoding. + +{% highlight bash %} +keytool -import -keystore $JAVA_HOME/jre/lib/security/cacerts -file tmp/cert.der -alias certName +{% endhighlight %} + +If you have multiple java editions installed on your machine, make sure that the app / web server is pointing to the correct JDK/JRE version (The one to which the certificate has been exported correctly) One common mistake that occurs while generating self-validated certificates is that the `JAVA_HOME` might be different than that used by the server. + + +###No subject alternative names present + +{% highlight bash %} +javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative names present +{% endhighlight %} + +This is a hostname/SSL certificate CN mismatch. This commonly happens when a self-signed certificate issued to localhost is placed on a machine that is accessed by IP address. It should be noted that generating a certificate with an IP address for a common name, e.g. CN=192.168.1.1,OU=Middleware,dc=vt,dc=edu, will not work in most cases where the client making the connection is Java. + + +###HTTPS hostname wrong +{% highlight bash %} +java.lang.RuntimeException: java.io.IOException: HTTPS hostname wrong: should be + org.jasig.cas.client.validation.Saml11TicketValidator.retrieveResponseFromServer(Saml11TicketValidator.java:203) + org.jasig.cas.client.validation.AbstractUrlBasedTicketValidator.validate(AbstractUrlBasedTicketValidator.java:185) + org.jasig.cas.client.validation.AbstractTicketValidationFilter.doFilter +{% endhighlight %} + +The above error occurs most commonly when the CAS client ticket validator attempts to contact the CAS server and is presented a certificate whose CN does not match the fully-qualified host name of the CAS server. There are a few common root causes of this mismatch: + +- CAS client misconfiguration +- Complex multi-tier server environment (e.g. clustered CAS server) +- Host name too broad for scope of wildcard certificate + +It is also worth checking that the certificate your CAS server is using for SSL encryption matches the one the client is checking against. + + +###Wildcard Certificates +Java support for wildcard certificates is limited to hosts strictly in the same domain as the wildcard. For example, a certificate with `CN=.vt.edu` matches hosts **`a.vt.edu`** and **`b.vt.edu`**, but *not* **`a.b.vt.edu`**. + + +###unrecognized_name Error +{% highlight bash %} +javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name +{% endhighlight %} + +The above error occurs mainly in Oracle JDK 7 CAS Server installations. In JDK7, SNI (Server Name Indication) is enabled by default. When the HTTPD Server does not send the correct Server Name back, the JDK HTTP Connection refuses to connect and the exception stated above is thrown. + +You must ensure your HTTPD Server is sending back the correct hostname. E.g. in Apache HTTPD, you must set the ServerAlias in the SSL vhost: + +{% highlight bash %} +ServerName your.ssl-server.name +ServerAlias your.ssl-server.name +{% endhighlight %} + +Alternatively, you can disable the SNI detection in JDK7, by adding this flag to the Java options of your CAS Servers' application server configuration: +{% highlight bash %} +-Djsse.enableSNIExtension=false +{% endhighlight %} + + +###When All Else Fails +If you have read, understood, and tried all the troubleshooting tips on this page and continue to have problems, please perform an SSL trace and attach it to a posting to the `cas-user@lists.jasig.org` mailing list. An SSL trace is written to STDOUT when the following system property is set, `javax.net.debug=ssl`. An example follows of how to do this in the Tomcat servlet container. + +Sample `setenv.sh` Tomcat Script follows: + +{% highlight bash %} +# Uncomment the next 4 lines for custom SSL keystore +# used by all deployed applications +#KEYSTORE="$HOME/path/to/custom.keystore" +#CATALINA_OPTS=$CATALINA_OPTS" -Djavax.net.ssl.keyStore=$KEYSTORE" +#CATALINA_OPTS=$CATALINA_OPTS" -Djavax.net.ssl.keyStoreType=BKS" +#CATALINA_OPTS=$CATALINA_OPTS" -Djavax.net.ssl.keyStorePassword=changeit" + +# Uncomment the next 4 lines to allow custom SSL trust store +# used by all deployed applications +#TRUSTSTORE="$HOME/path/to/custom.truststore" +#CATALINA_OPTS=$CATALINA_OPTS" -Djavax.net.ssl.trustStore=$TRUSTSTORE" +#CATALINA_OPTS=$CATALINA_OPTS" -Djavax.net.ssl.trustStoreType=BKS" +#CATALINA_OPTS=$CATALINA_OPTS" -Djavax.net.ssl.trustStorePassword=changeit" + +# Uncomment the next line to print SSL debug trace in catalina.out +#CATALINA_OPTS=$CATALINA_OPTS" -Djavax.net.debug=ssl" + +export CATALINA_OPTS +{% endhighlight %} + +##Review logs +CAS server logs are the best resource for determining the root cause of the problem, provided you have configured the appropriate log levels. Specifically you want to make sure `DEBUG` levels are turned on the `org.jasig` package in the log configuration: + +{% highlight xml %} + + + + +{% endhighlight %} + +When changes are applied, restart the server environment and observe the log files to get a better understanding of CAS behavior. For more info, please [review the this guide](Logging.html) on how to configure logs with CAS. + + diff --git a/cas-server-documentation/installation/Trusted-Authentication.md b/cas-server-documentation/installation/Trusted-Authentication.md new file mode 100644 index 000000000000..0d7d071c51a2 --- /dev/null +++ b/cas-server-documentation/installation/Trusted-Authentication.md @@ -0,0 +1,66 @@ +--- +layout: default +title: CAS - Trusted Authentication +--- + +# Trusted Authentication +The trusted authentication handler provides support for trusting authentication performed by some other component +in the HTTP request handling chain. Proxies (including Apache in a reverse proxy scenario) are the most common +components that perform authentication in front of CAS. + +Trusted authentication handler support is enabled by including the following dependency in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-support-trusted + ${cas.version} + +{% endhighlight %} + + +## Configure Trusted Authentication Handler +Modify `deployerConfigContext.xml` according to the following template: + +{% highlight xml %} + + + + + + + + + + + + + + + + +{% endhighlight %} + + +## Configure Webflow Components +Add an additional state to `login-webflow.xml`: + +{% highlight xml %} + + + + + +{% endhighlight %} + +Replace references to `viewLoginForm` in existing states with `remoteAuthenticate`. + +Install the Webflow action into the Spring context by adding the following bean to `cas-servlet.xml`: +{% highlight xml %} + +{% endhighlight %} diff --git a/cas-server-documentation/installation/User-Interface-Customization.md b/cas-server-documentation/installation/User-Interface-Customization.md new file mode 100644 index 000000000000..b39c6358521a --- /dev/null +++ b/cas-server-documentation/installation/User-Interface-Customization.md @@ -0,0 +1,380 @@ +--- +layout: default +title: CAS - User Interface Customization +--- + + +#Overview +Branding the CAS User Interface (UI) involves simply editing the CSS stylesheet and also a small collection of relatively simple JSP include files, +also known as views. Optionally, you may also wish to modify the text displayed and/or add additional Javascript effects on these views. + +All the files that we'll be discussing in this section that concern the theme are located in and referenced from: `/cas-server-webapp/src/main/webapp`. + +#Browser Support +CAS user interface should properly and comfortably lend itself to all major browser vendors: + +* Google Chrome +* Mozilla Firefox +* Apple Safari +* Microsoft Internet Explorer + +Note that certain older version of IE, particularly IE 9 and below may impose additional difficulty in getting the right UI configuration in place. + +## Internet Explorer +To instruct CAS to render UI in compatibility mode, add the following to relevant UI components: + +{% highlight jsp %} + +{% endhighlight %} + +#Getting Started + +##CSS +The default styles are all contained in a single file located in `css/cas.css`. This location is set in `WEB-INF/classes/cas-theme-default.properties`. If you would like to create your own `css/custom.css file`, for example, you will need to update `standard.custom.css.file` key in that file. + +{% highlight bash %} +standard.custom.css.file=/css/cas.css +cas.javascript.file=/js/cas.js +{% endhighlight %} + + +###CSS per Locale +Selecting CSS files per enabled locale would involve changing the `top.jsp` file to include the below sample code: + +{% highlight jsp %} +<% + String cssFileName = "cas.css"; // default + Locale locale = request.getLocale(); + + if (locale != null && locale.getLanguage() != null){ + String languageCssFileName = "cas_" + locale.getLanguage() + ".css"; + cssFileName = languageCssFileName; //ensure this file exists + } + +%> + +{% endhighlight %} + + +###Responsive Design +CSS media queries bring responsive design features to CAS which would allow adopter to focus on one theme for all appropriate devices and platforms. These queries are defined in the same `css/cas.css` file. Below follows an example: + +{% highlight css %} +@media only screen and (max-width: 960px) { + footer { padding-left: 10px; } +} + +@media only screen and (max-width: 799px) { + header h1 { font-size: 1em; } + #login { float: none; width: 100%; } + #fm1 .row input[type=text], + #fm1 .row input[type=password] { width: 100%; padding: 10px; box-sizing: border-box; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; } + #fm1 .row .btn-submit { outline: none; -webkit-appearance: none; -webkit-border-radius: 0; border: 0; background: #210F7A; color: white; font-weight: bold; width: 100%; padding: 10px 20px; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } + #fm1 .row .btn-reset { display: none; } + #sidebar { margin-top: 20px; } + #sidebar .sidebar-content { padding: 0; } +} +{% endhighlight %} + + +##Javascript +If you need to add some JavaScript, feel free to append `js/cas.js`. + +You can also create your own `custom.js` file, for example, and call it from within `WEB-INF/view/jsp/default/ui/includes/bottom.jsp` like so: + + +{% highlight html %} + +{% endhighlight %} + +If you are developing themes per service, each theme also has the ability to specify a custom `cas.js` file under the `cas.javascript.file` setting. + +The following Javascript libraries are utilized by CAS automatically: + +* JQuery +* JQuery UI +* JQuery Cookie +* [JavaScript Debug](http://benalman.com/projects/javascript-debug-console-log/): A simple wrapper for `console.log()` + +###Asynchronous Script Loading +CAS will attempt load the aforementioned script libraries asynchronously so as to not block the page rendering functionality. +The loading of script files is handled by the [`head.js` library](http://headjs.com) and is the responsibility of `cas.js` file: + +{% highlight javascript %} +var scripts = [ "...", "..."]; +head.ready(document, function() { + head.load(scripts, resourceLoadedSuccessfully); +}); + +function resourceLoadedSuccessfully() { + ... +} +{% endhighlight %} + +The only script that is loaded synchronously is the `head.js` library itself. + +Because scripts, and specially JQuery are loaded asynchronously, any custom Javascript that is placed inside the page +that relies on these libraries may not immediately function on page load. CAS provides a callback function that allows +adopters to be notified when script loading has completed and this would be a safe time to execute/load other Javascript-related +functions that depend on JQuery inside the actual page. + +{% highlight javascript %} +function jqueryReady() { + //Custom Javascript tasks can be carried out now via JQuery... +} +{% endhighlight %} + + +###Checking CAPSLOCK +CAS will display a brief warning when the CAPSLOCK key is turned on during the typing of the credential password. This check +is enforced by the `cas.js` file. + +{% highlight javascript %} +$('#password').keypress(function(e) { + var s = String.fromCharCode( e.which ); + if ( s.toUpperCase() === s && s.toLowerCase() !== s && !e.shiftKey ) { + $('#capslock-on').show(); + } else { + $('#capslock-on').hide(); + } +}); +} +{% endhighlight %} + +###Browser Cookie Support +For CAS to honor a single sign-on session, the browser MUST support and accept cookies. CAS will notify the +user if the browser has turned off its support for cookies. This behavior is controlled via the `cas.js` file. + +{% highlight javascript %} +function areCookiesEnabled() { + $.cookie('cookiesEnabled', 'true'); + var value = $.cookie('cookiesEnabled'); + if (value != undefined) { + $.removeCookie('cookiesEnabled'); + return true; + } + return false; +} +{% endhighlight %} + +###Preserving Anchor Fragments +Anchors/fragments may be lost across redirects as the server-side handler of the form post ignores the client-side anchor, unless appended to the form POST url. +This is needed if you want a CAS-authenticated application to be able to use anchors/fragments when bookmarking. + +####Changes to `cas.js` +{% highlight javascript %} +/** + * Prepares the login form for submission by appending any URI + * fragment (hash) to the form action in order to propagate it + * through the re-direct (i.e. store it client side). + * @param form The login form object. + * @returns true to allow the form to be submitted. + */ +function prepareSubmit(form) { + // Extract the fragment from the browser's current location. + var hash = decodeURIComponent(self.document.location.hash); + + // The fragment value may not contain a leading # symbol + if (hash && hash.indexOf("#") === -1) { + hash = "#" + hash; + } + + // Append the fragment to the current action so that it persists to the redirected URL. + form.action = form.action + hash; + return true; +} +{% endhighlight %} + + +####Changes to Login Form + +{% highlight jsp %} + +{% endhighlight %} + +## JSP +The default views are found at `WEB-INF/view/jsp/default/ui/`. + +Notice `top.jsp` and `bottom.jsp` include files located in the `../includes` directory. These serve as the layout template for the other JSP files, which get injected in between during compilation to create a complete HTML page. + +####Tag Libraries +The following JSP tag libraries are used by the user interface: + +{% highlight jsp %} +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> +{% endhighlight %} + + +####Glossary of Views + +| View | Description +|-----------------------------------+--------------------------------------------------------------------------------+ +| `casAccountDisabledView` | Specific to Password Policy Enforcement; displayed in the event that authentication encounters an account that is disabled in the underlying account store (i.e. LDAP) +| `casAccountLockedView` | Specific to Password Policy Enforcement; displayed in the event that authentication encounters an account that is locked in the underlying account store (i.e. LDAP) +| `casBadHoursView` | Specific to Password Policy Enforcement; displayed when authentication encounters an account that is not allowed authentication within the current time window in the underlying account store (i.e. LDAP) +| `casBadWorkstationView` | Specific to Password Policy Enforcement; displayed when authentication encounters an account that is not allowed authentication from the current workstation in the underlying account store (i.e. LDAP) +| `casExpiredPassView` | Specific to Password Policy Enforcement; displayed in the event that authentication encounters an account that has expired in the underlying account store (i.e. LDAP) +| `casMustChangePassView` | Specific to Password Policy Enforcement; displayed in the event that authentication encounters an account that must change its password in the underlying account store (i.e. LDAP) +| `casWarnPassView` | Specific to Password Policy Enforcement; displayed when the user account is near expiration based on specified configuration (i.e. LDAP) +| `casConfirmView` | Displayed when the user is warned before being redirected to the service. This allows users to be made aware whenever an application uses CAS to log them in. (If they don't elect the warning, they may not see any CAS screen when accessing an application that successfully relies upon an existing CAS single sign-on session.) Some CAS adopters remove the 'warn' checkbox in the CAS login view and don't offer this interstitial advisement that single sign-on is happening. +| `casGenericSuccess` | Displayed when the user has been logged in without providing a service to be redirected to. +| `casLoginView` | Main login form. +| `casLogoutView` | Main logout view. +| `serviceErrorView` | Used in conjunction with the service registry feature, displayed when the service the user is trying to access is not allowed to use CAS. The default in-memory services registry configuration, in 'deployerConfigContext.xml', allows all users to obtain a service ticket to access all services. +| `serviceErrorSsoView` | Displayed when a user would otherwise have experienced non-interactive single sign-on to a service that is, per services registry configuration, disabled from participating in single sign-on. (In the default services registry registrations, all services are permitted to participate in single sign-on, so this view will not be displayed.) + + +####Glossary of Monitoring Views +The monitoring views are found at `WEB-INF/view/jsp/monitoring/`. + + +| View | Description +|-----------------------------------+--------------------------------------------------------------------------------+ +| `viewConfig` | Displayed when user attempts to view the state of the CAS application runtime and its configuration. +| `viewSsoSessions` | Displayed when user wishes to view the Single Sign-on Report. +| `viewStatistics` | Displayed when user wishes review the CAS server statistics. + + +####Glossary of System Error Views +The error views are found at `WEB-INF/view/jsp/`. + +| View | Description +|-----------------------------------+--------------------------------------------------------------------------------+ +| `errors` | Displayed when CAS experiences an error it doesn't know how to handle (an unhandled Exception). For instance, CAS might be unable to access a database backing the services registry. This is the generic CAS error page. It's important to brand it to provide an acceptable error experience to your users. +| `authorizationFailure` | Displayed when a user successfully authenticates to the services management web-based administrative UI included with CAS, but the user is not authorized to access that application. + +###Warning Before Accessing Application +CAS has the ability to warn the user before being redirected to the service. This allows users to be made aware whenever an application uses CAS to log them in. +(If they don't elect the warning, they may not see any CAS screen when accessing an application that successfully relies upon an existing CAS single sign-on session.) +Some CAS adopters remove the 'warn' checkbox in the CAS login view and don't offer this interstitial advisement that single sign-on is happening. + +{% highlight jsp %} +... +" type="checkbox" /> + +... +{% endhighlight %} + +###"I am at a public workstation" authentication +CAS has the ability to allow the user to opt-out of SSO, by indicating on the login page that the authentication +is happening at a public workstation. By electing to do so, CAS will not honor the subsequent SSO session +and will not generate the TGC that is designed to do so. + +{% highlight jsp %} +... + + +... +{% endhighlight %} + +##Localization +The CAS Web application includes a number of localized message files: + +- English (US) +- Spanish +- French +- Russian +- Netherlands (Nederlands) +- Swedish (Svenskt) +- Italian (Italiano) +- Urdu +- Chinese (Simplified) +- Dutch (Deutsch) +- Japanese +- Croatian +- Czech +- Slovenian +- Polish +- Portuguese (Brazil) +- Turkish +- Farsi +- Arabic + +In order to "invoke" a specific language for the UI, the `/login` endpoint may be passed a `locale` parameter as such: + +{% highlight jsp %} +https://cas.server.edu/login?locale=it +{% endhighlight %} + +Note that not all languages are complete and accurate across CAS server releases as translations are entirely dependent upon community contributions. +For an accurate and complete list of localized messages, always refer to the English language bundle. + +###Configuration +All message bundles are marked under `messages_xx.properties` files at `WEB-INF/classes`. The default language bundle is for the +English language and is thus called `messages.properties`. If there are any custom messages that need to be presented into views, +they may also be formatted under `custom_messages.properties` files. + +Messages are parsed and loaded via the following configuration: + +{% highlight xml %} + + + + classpath:custom_messages + classpath:messages + +{% endhighlight %} + +Messages are then read on each JSP view via the following sample configuration: + +{% highlight jsp %} + +{% endhighlight %} + +In the event that the code is not found in the activated resource bundle, the code itself will be used verbatim. + + +##Themes +With the introduction of [Service Management application](Service-Management.html), deployers are now able to switch the themes based on different services. For example, you may want to have different login screens (different styles) for staff applications and student applications. Or, you want to show two layouts for day time and night time. This document could help you go through the basic settings to achieve this. + +Note that support for themes comes with the following components: + +| Component | Description +|--------------------------------+--------------------------------------------------------------------------------+ +| `ServiceThemeResolver` | can be configured to decorate CAS views based on the `theme` property of a given registered service in the Service Registry. The theme that is activated via this method will still preserve the default JSP views for CAS but will simply apply decorations such as CSS and Javascript to the views. The physical structure of views cannot be modified via this method. +| `RegisteredServiceThemeBasedViewResolver` | If there is a need to present an entirely new set of views for a given service, such that the structure and layout of the page needs an overhaul with additional icons, images, text, etc then this component` needs to be configured. This component will have the ability to resolve a new set of views that may entirely be different from the default JSPs. The `theme` property of a given registered service in the Service Registry will still need to be configured to note the set of views that are to be loaded. + + +###`ServiceThemeResolver` +Configuration of service-specific themes is backed by the Spring framework and provided by the following component: +{% highlight xml %} + +{% endhighlight %} + +Furthermore, deployers may be able to use the functionality provided by the `ThemeChangeInterceptor` of Spring framework to provide theme configuration per each request. + +####Configuration +- Add another theme properties file, which must be placed to the root of `/WEB-INF/classes` folder, name it as `theme_name.properties`. Contents of this file should match the `cas-theme-default.properties` file. +- Add the location of related styling files, such as CSS and Javascript in the file above. +- Specify the name of your theme for the service definition under the `theme` property. + +###`RegisteredServiceThemeBasedViewResolver` +`RegisteredServiceThemeBasedViewResolver` is an alternate Spring View Resolver that utilizes a service's +associated theme to selectively choose which set of UI views will be used to generate the standard views (`casLoginView.jsp`, etc). This is specially useful in cases where the set of pages for a theme that are targeted +for a different type of audience are entirely different structurally that simply +using the `ServiceThemeResolver` is not practical to augment the default views. In such cases, new view pages may be required. + +Views associated with a particular theme by default are expected to be found at: `/WEB-INF/view/jsp//ui/` + +{% highlight xml %} + +{% endhighlight %} + +####Configuration +- Clone the default set of view pages into a new directory based on the theme id (i.e. `/WEB-INF/view/jsp//ui/`). +- Specify the name of your theme for the service definition under the `theme` property. + + diff --git a/cas-server-documentation/installation/Webflow-Customization.md b/cas-server-documentation/installation/Webflow-Customization.md new file mode 100644 index 000000000000..8980c8de19a7 --- /dev/null +++ b/cas-server-documentation/installation/Webflow-Customization.md @@ -0,0 +1,271 @@ +--- +layout: default +title: CAS - Web Flow Customization +--- + + +#Webflow Customization +CAS uses [Spring Web Flow](projects.spring.io/spring-webflow) to do "script" processing of login and logout protocols. Spring Web Flow builds on Spring MVC and allows implementing the "flows" of a web application. A flow encapsulates a sequence of steps that guide a user through the execution of some business task. It spans multiple HTTP requests, has state, deals with transactional data, is reusable, and may be dynamic and long-running in nature. Each flow may contain among many other settings the following major elements: + +- Actions: components that describe an executable task and return back a result +- Transitions: Routing the flow from one state to another; Transitions may be global to the entire flow. +- Views: Components that describe the presentation layer displayed back to the client +- Decisions: Components that conditionally route to other areas of flow and can make logical decisions + +Spring Web Flow presents CAS with a pluggable architecture where custom actions, views and decisions may be injected into the flow to account for additional use cases and processes. Note that to customize the weblow, one must possess a reasonable level of understanding of the webflow's internals and injection policies. The intention of this document is not to describe Spring Web Flow, but merely to demonstrate how the framework is used by CAS to carry out various aspects of the protocol and business logic execution. + + +##Login Flow +The flow in CAS is given a unique id that is registered inside a `flowRegistry` component. Support is enabled via the following configuration snippets in `cas-servlet.xml`: + + +###Components +{% highlight xml %} + + + +... + + + + + +{% endhighlight %} + +###login-flow.xml Overview +The login flow is at a high level composed of the following phases: + +- Initialization of the flow +- Validation of Ticket Granting Ticket (TGT) +- Validation of requesting service and ensuring that it is authorized to use CAS +- Generation of the Login Ticket (LT) +- Presentation of the Login Form +- Generation of TGT upon successful authentication +- Generation of Service Ticket (ST) for the requesting service +- Issuing a redirect back to the authenticating web service + +A high-level diagram detailing major states in the flow is presented here: + +![](http://i.imgur.com/SBDUGbH.png) + +Acceptance of user credentials and invoking the authentication handler components is carried out by: + +{% highlight xml %} + + +{% endhighlight %} + +Handling authentication failures, mapping the result of which event to a new state is carried out by: + +{% highlight xml %} + + +... + + + + + + + + + +.... + + + + + + + + + + + +{% endhighlight %} + +Certain error conditions are also classified as global transitions, particularly in cases of unauthorized services attempting to use CAS: + +{% highlight xml %} + + + + + +{% endhighlight %} + + +##Logout Flow +The flow in CAS is given a unique id that is registered inside a `flowRegistry` component. Support is enabled via the following configuration snippets in `cas-servlet.xml`: + +###Components +{% highlight xml %} + + + + + + + +... + + + + + +{% endhighlight %} + +###logout-flow.xml Overview +The logout flow is at a high level composed of the following phases: + +- Termination of the SSO session and destruction of the TGT +- Initiating the Logout protocol +- Handling various methods of logout (front-channel, back-channel, etc) + +The Logout protocol is initiated by the following component: + +{% highlight xml %} + + +{% endhighlight %} + +Front-channel method of logout is specifically handled by the following component: + +{% highlight xml %} + +{% endhighlight %} + + +##Termination of Web Flow Sessions +CAS provides a facility for storing flow execution state on the client in Spring Webflow. Flow state is stored as an encoded byte stream in the flow execution identifier provided to the client when rendering a view. The following features are presented via this strategy: + +- Support for conversation management (e.g. flow scope) +- Encryption of encoded flow state to prevent tampering by malicious clients + +By default, the conversational state of Spring Webflow is managed inside the application session, which can time out due to inactivity and must be cleared upon the termination of flow. Rather than storing this state inside the session, CAS automatically attempts to store and keep track of this state on the client in an encrypted form to remove the need for session cleanup, termination and replication. + +{% highlight xml %} + + +{% endhighlight %} + +Default encryption strategy controlled via the `loginFlowStateTranscoder` component is using the 128-bit AES in CBC ciphering mode with compression turned on. These settings can be controlled via the following settings defined in the `cas.properties` file: + +{% highlight properties %} +# cas.webflow.cipher.alg=AES +# cas.webflow.cipher.mode=CBC +# cas.webflow.cipher.padding=PKCS7 +# cas.webflow.keystore=classpath:/etc/keystore.jceks +# cas.webflow.keystore.type=JCEKS +# cas.webflow.keystore.password=changeit +# cas.webflow.keyalias=aes128 +# cas.webflow.keypassword=changeit +{% endhighlight %} + +

Usage Warning!

+While the above settings are all optional, it is recommended that you provide your own configuration and settings for encrypting and transcoding of the web session state.

+ +##Required Service for Authentication Flow +By default, CAS will present a generic success page if the initial authentication request does not identify +the target application. In some cases, the ability to login to CAS without logging +in to a particular service may be considered a misfeature because in practice, too few users and institutions +are prepared to understand, brand, and support what is at best a fringe use case of logging in to CAS for the +sake of establishing an SSO session without logging in to any CAS-reliant service. + +As such, CAS optionally allows adopters to not bother to prompt for credentials when no target application is presented +and instead presents a message when users visit CAS directly without specifying a service. + +This behavior is controlled via `cas.properties`: + +{% highlight properties %} +# Indicates whether an SSO session can be created if no service is present. +# create.sso.missing.service=false +{% endhighlight %} + +##Extending the Webflow +The CAS webflow provides discrete points to inject new functionality. Thus, the only thing to modify is the flow definition where new beans and views can be added easily with the Maven overlay build method. + + +###Adding Actions +Adding Spring Web Flow actions typically involves the following steps: + +- Adding a SWF-specific Spring bean type that extends `org.springframework.webflow.action.AbstractAction` +- Access to the flow scope object also accessible in the flow definition file +- `doExecute()` method contains business logic and returns `success()` or `error()` Event types +- Returned Event types are evaluated in the flow definition file + +Once the action bean is configured, you may define it inside the `login-webflow.xml`: + +{% highlight xml %} + + + + + +{% endhighlight %} + + +###Adding Views +Adding Spring Web Flow views involves the following steps: + +- The name of the view directly referenced from the flow definition file +- The name of the view must match the view file name + +A sample is presented here: + +{% highlight bash %} +### View for password update +passwordUpdateView.(class)=org.springframework.web.servlet.view.JstlView +passwordUpdateView.url=/WEB-INF/view/jsp/default/ui/passwordUpdateView.jsp + +{% endhighlight %} + +##Acceptable Usage Policy Flow +CAS presents the ability to allow the user to accept the usage policy before moving on to the application. The task of remembering the user's choice is kept in memory by default and will be lost upon container restarts and/or in clustered deployments. Production-level deployments of this feature would require modifications to the flow such that the retrieval and/or acceptance of the policy would be handled via an external storage mechanism such as LDAP or JDBC. + +###Configuration + +####Enable Webflow + +- In the `login-webflow.xml` file, enable the transition to `acceptableUsagePolicyCheck` by uncommenting the following entry: + +{% highlight xml %} + +{% endhighlight %} + +- Enable the actual flow components by uncommenting the following entries: + +{% highlight xml %} + +{% endhighlight %} + +- Customize the policy by modifying `casAcceptableUsagePolicyView.jsp` located at `src/main/webapp/WEB-INF/view/jsp/default/ui`. + +####Configure Storage + +The task of remembering and accepting the policy is handled by `AcceptableUsagePolicyFormAction`. Adopters may extend this class to retrieve and persistent the user's choice via an external backend mechanism such as LDAP or JDBC. + +{% highlight xml %} + +{% endhighlight %} diff --git a/cas-server-documentation/installation/Whitelist-Authentication.md b/cas-server-documentation/installation/Whitelist-Authentication.md new file mode 100644 index 000000000000..50adc5c11352 --- /dev/null +++ b/cas-server-documentation/installation/Whitelist-Authentication.md @@ -0,0 +1,52 @@ +--- +layout: default +title: CAS - Whitelist Authentication +--- + + +# Whitelist Authentication +Whitelist authentication components fall into two categories: Those that accept a set of credentials stored directly in the configuration and those that accept a set of credentials from a file resource on the server. + +These are: +* `AcceptUsersAuthenticationHandler` +* `FileAuthenticationHandler` + + +## Authentication Components +Support is enabled by including the following dependency in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-support-generic + ${cas.version} + +{% endhighlight %} + +###`AcceptUsersAuthenticationHandler` +{% highlight xml %} + + + + + + + +{% endhighlight %} + + + +###`FileAuthenticationHandler` +{% highlight xml %} + +{% endhighlight %} + + +####Example Password File +{% highlight bash %} +scott::password +bob::password2 +{% endhighlight %} + + diff --git a/cas-server-documentation/installation/X509-Authentication.md b/cas-server-documentation/installation/X509-Authentication.md new file mode 100644 index 000000000000..8fbfb7e1cd2b --- /dev/null +++ b/cas-server-documentation/installation/X509-Authentication.md @@ -0,0 +1,390 @@ +--- +layout: default +title: CAS - X.509 Authentication +--- + +# X.509 Authentication +CAS X.509 authentication components provide a mechanism to authenticate users who present client certificates during +the SSL/TLS handshake process. The X.509 components require configuration ouside the CAS application since the +SSL handshake happens outside the servlet layer where the CAS application resides. There is no particular requirement +on deployment architecture (i.e. Apache reverse proxy, load balancer SSL termination) other than any client +certificate presented in the SSL handshake be accessible to the servlet container as a request attribute named +`javax.servlet.request.X509Certificate`. This happens naturally for configurations that terminate SSL connections +directly at the servlet container and when using Apache/mod_jk; for other architectures it may be necessary to do +additional work. + + +## X.509 Components +X.509 support is enabled by including the following dependency in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-support-x509 + ${cas.version} + +{% endhighlight %} + +CAS provides an X.509 authentication handler, a handful of X.509-specific principal resolvers, some certificate +revocation machinery, and some Webflow actions to provide for non-interactive authentication. + +######`X509CredentialsAuthenticationHandler` +The X.509 handler technically performs additional checks _after_ the real SSL client authentication process performed +by the Web server terminating the SSL connection. Since an SSL peer may be configured to accept a wide range of +certificates, the CAS X.509 handler provides a number of properties that place additional restrictions on +acceptable client certificates. + +* `trustedIssuerDnPattern` - Regular expression defining allowed issuer DNs. (must be specified) +* `subjectDnPattern` - Regular expression defining allowed subject DNs. (default=`.*`) +* `maxPathLength` - Maximum number of certs allowed in certificate chain. (default=1) +* `maxPathLengthAllowUnspecified` - True to allow unspecified path length, false otherwise. (default=false) +* `checkKeyUsage` - True to enforce certificate `keyUsage` field (if present), false otherwise. (default=false) +* `requireKeyUsage` - True to require the existence of a `keyUsage` certificate field, false otherwise. (default=false) +* `revocationChecker` - Instance of `RevocationChecker` used for certificate expiration checks. +(default=`NoOpRevocationChecker`) + + +### Principal Resolver Components + +######`X509SubjectPrincipalResolver` +Creates a principal ID from a format string composed of components from the subject distinguished name. +The following configuration snippet produces prinicpals of the form _cn@example.com_. For example, given a +certificate with the subject _DC=edu, DC=vt/UID=jacky, CN=Jascarnella Ellagwonto_ it would produce the ID +_jacky@vt.edu_. + +{% highlight xml %} + +{% endhighlight %} + +See the Javadocs for a thorough discussion of the format string specification. + + +######`X509SubjectDNPrincipalResolver` +Creates a principal ID from the certificate subject distinguished name. + + +######`X509SerialNumberPrincipalResolver` +Creates a principal ID from the certificate serial number. + + +######`X509SerialNumberAndIssuerDNPrincipalResolver` +Creates a principal ID by concatenating the certificate serial number, a delimiter, and the issuer DN. +The serial number may be prefixed with an optional string. See the Javadocs for more information. + +######`X509SubjectAlternativeNameUPNPrincipalResolver` +Adds support the embedding of a `UserPrincipalName` object as a `SubjectAlternateName` extension within an X509 certificate, +allowing properly-empowered certificates to be used for network logon (via SmartCards, or alternately by 'soft certs' in certain environments). +This resolver extracts the Subject Alternative Name UPN extension from the provided certificate if available as a resolved principal id. + +{% highlight xml %} + + + + + ... + + + +... + + + + +{% endhighlight %} + + +### Certificate Revocation Checking Components +CAS provides a flexible policy engine for certificate revocation checking. This facility arose due to lack of +configurability in the revocation machinery built into the JSSE. + +The following configuration is shared by all components: + +| Field | Description +|-----------------------------------+---------------------------------------------------------+ +| `unavailableCRLPolicy` | Policy applied when CRL data is unavailable upon fetching. (default=`DenyRevocationPolicy`) +| `expiredCRLPolicy` | Policy applied when CRL data is expired. (default=`ThresholdExpiredCRLRevocationPolicy`) + +The following policies are available by default: + +| Policy | Description +|-----------------------------------+---------------------------------------------------------+ +| `AllowRevocationPolicy` | Allow policy +| `DenyRevocationPolicy` | Deny policy +| `ThresholdExpiredCRLRevocationPolicy` | Deny if CRL is more than X seconds expired. + + +####`ResourceCRLRevocationChecker` +Performs a certificate revocation check against a CRL hosted at a fixed location. Any resource type supported by the +Spring [`Resource`]() class may be specified for the CRL resource. The CRL is fetched at periodic intervals and cached. + +Configuration properties: + +| Field | Description +|-----------------------------------+---------------------------------------------------------+ +| `crls` | Spring resource describing the location/kind of CRL resource. This MUST be specified. A single CRL resource can be alternatively specified via the `crl` parameter. +| `refreshInterval` | Periodic CRL refresh interval in seconds. (default=3600) +| `fetcher` | Component responsible for fetching of the CRL resource. (default=`ResourceCRLFetcher`) + + +`ResourceCRLRevocationChecker` Example: +{% highlight xml %} + + + + + + + +{% endhighlight %} + + +####`CRLDistributionPointRevocationChecker` +Performs certificate revocation checking against the CRL URI(s) mentioned in the certificate _cRLDistributionPoints_ +extension field. The component leverages a cache to prevent excessive IO against CRL endpoints; CRL data is fetched +if does not exist in the cache or if it is expired. + +Configuration properties: + +| Field | Description +|-----------------------------------+---------------------------------------------------------+ +| `cache` | Ehcache `Cache` component. +| `fetcher` | Component responsible for fetching of the CRL resource. (default=`ResourceCRLFetcher`) +| `throwOnFetchFailure` | Throws errors if fetching of the CRL resource fails. +| `checkAll` | Flag to indicate whether all CRLs should be checked for the certificate resource. + +`CRLDistributionPointRevocationChecker` Example: + +{% highlight xml %} + + + + + + + + + + + + +{% endhighlight %} + +#### CRL Fetching Configuration +By default, all revocation checks use the `ResourceCRLFetcher` component to fetch the CRL resource from the specified location. The following alternatives are available: + +#####`LdaptiveResourceCRLFetcher` +Fetches a CRL resource from a preconfigured attribute, in the event that the CRL resource is an LDAP instance: + + +#####`PoolingLdaptiveResourceCRLFetcher` +Fetches a CRL resource from a preconfigured attribute, in the event that the CRL resource is an LDAP instance. This component is able to use connection pooling. + +##### Example Configuration +The following example demonstrates the configuration required to fetch a CRL resource from LDAP. Both fetchers are demonstrated here, but only one generally is necessary. + +The example below searches an LDAP instance found in the X509 certificate, based on the filter and the baseDN given. It then attempts to obtain the binary attribute `certificateRevocationList` and fetch the resource. The value will be decoded to Base64 first by the fetcher. + +{% highlight xml %} + + + + + + + + + + + + + + + + certificateRevocationList + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{% endhighlight %} + +### Webflow Components +A single Webflow component, `X509CertificateCredentialsNonInteractiveAction`, is required to extract the certificate +from the HTTP request context and perform non-interactive authentication. + + +## X.509 Configuration +X.509 configuration requires substantial configuration outside the CAS Web application. The configuration of Web +server SSL components varies dramatically with software and is outside the scope of this document. We offer some +general advice for SSL configuration: + +* Configuring SSL components for optional client certificate behavior generally provides better user experience. +Requiring client certificates prevents SSL negotiation in cases where the certificate is not present, which prevents +user-friendly server-side error messages. +* Accept certificates only from trusted issuers, generally those within your PKI. +* Specify all certificates in the certificate chain(s) of allowed issuers. + + +### Configure Authentication Components +Use the following template to configure authentication in `deployerConfigContext.xml`: +{% highlight xml %} + + + + + + + + + + + + + + + + + + + + + + + + +{% endhighlight %} + + +### X.509 Webflow Configuration +Uncomment the `startAuthenticate` state in `login-webflow.xml`: + +{% highlight xml %} + + + + + + +{% endhighlight %} + +Replace all instances of the `generateLoginTicket` transition in other states with `startAuthenticate`. + +Define the `x509Check` bean in `cas-servlet.xml`: +{% highlight xml %} + +{% endhighlight %} diff --git a/cas-server-documentation/integration/Attribute-Release.md b/cas-server-documentation/integration/Attribute-Release.md new file mode 100644 index 000000000000..8f78b1a83851 --- /dev/null +++ b/cas-server-documentation/integration/Attribute-Release.md @@ -0,0 +1,353 @@ +--- +layout: default +title: CAS - Attribute Release +--- + +# Attribute Release +Resolved attributes are returned to scoped services via the [SAML 1.1 protocol](../protocol/SAML-Protocol.html) or the [CAS protocol](../protocol/CAS-Protocol.html). + +Attributes pass through a two-step process: + +* [Attribute Resolution](Attribute-Resolution.html): Done at the time of establishing the principal, *usually* via `PrincipalResolver` components where attributes are resolved from various sources. +* Attribute Release: Adopters must explicitly configure attribute release for services in order for the resolved attributes to be released to a service in the validation response. + + +
Service Management

Attribute release may also be configured via the +Service Management tool.

+ + +## Configuration +Once principal attributes are [resolved](Attribute-Resolution.html), adopters may choose to allow/release each attribute per each definition in the registry. Example configuration follows: + +{% highlight xml %} + + + + + + + + + + uid + groupMembership + memberOf + + + + + +{% endhighlight %} + + +### Principal-Id Attribute +The service registry component of CAS has the ability to allow for configuration of a `usernameAttributeProvider` to be returned for the given registered service. When this property is set for a service, CAS will return the value of the configured attribute as part of its validation process. + +* Ensure the attribute is available and resolved for the principal +* Set the `usernameAttributeProvider` property of the given service to once of the attribute providers below + +####`DefaultRegisteredServiceUsernameProvider` +The default configuration which need not explicitly be defined, simply returns the resolved principal id as the username for this service. + +{% highlight xml %} + + + + + + + + + + +{% endhighlight %} + +####`PrincipalAttributeRegisteredServiceUsernameProvider` +Returns an attribute that is already resolved for the principal as the username for this service. If the attribute +is not available, the default principal id will be used. + +{% highlight xml %} + + + + + + + + + + +{% endhighlight %} + +####`AnonymousRegisteredServiceUsernameAttributeProvider` +Provides an opaque identifier for the username. The opaque identifier by default conforms to the requirements +of the [eduPersonTargetedID](http://www.incommon.org/federation/attributesummary.html#eduPersonTargetedID) attribute. + +{% highlight xml %} + + + + + + + + + + +{% endhighlight %} + + +### Attribute Release Policy +The release policy decides how attributes are to be released for a given service. Each policy has the ability to apply an optional filter. + +The following settings are shared by all attribute release policies: + +| Field | Description +|-----------------------------------+--------------------------------------------------------------------------------+ +| `authorizedToReleaseCredentialPassword` | Boolean to define whether the service is authorized to [release the credential as an attribute](ClearPass.html). +| `authorizedToReleaseProxyGrantingTicket` | Boolean to define whether the service is authorized to [release the proxy-granting ticket id as an attribute](../installation/Configuring-Proxy-Authentication.html) + +#### Components + +#####`ReturnAllAttributeReleasePolicy` +Return all resolved attributes to the service. + +{% highlight xml %} + + ... + + + + +{% endhighlight %} + +#####`ReturnAllowedAttributeReleasePolicy` +Only return the attributes that are explicitly allowed by the configuration. + +{% highlight xml %} + + ... + + + + + uid + groupMembership + memberOf + + + + + +{% endhighlight %} + + +#####`ReturnMappedAttributeReleasePolicy` +Similar to above, this policy will return a collection of allowed attributes for the service, but also allows those attributes to be mapped and "renamed" at the more granular service level. + +For example, the following configuration will recognize the resolved attributes `uid`, `eduPersonAffiliation` and `groupMembership` and will then release `uid`, `affiliation` and `group` to the web application configured. + +{% highlight xml %} + + ... + + + + + + + + + + + + +{% endhighlight %} + + +#### Attribute Filters +While each policy defines what attributes may be allowed for a given service, there are optional attribute filters that can be set per policy to further weed out attributes based on their **values**. + +######`RegisteredServiceRegexAttributeFilter` +The regex filter that is responsible to make sure only attributes whose value matches a certain regex pattern are released. + +Suppose that the following attributes are resolved: + +| Name | Value +|---------------------------------------+---------------------------------------------------------------+ +| `uid` | jsmith +| `groupMembership` | std +| `cn` | JohnSmith + +The following configuration for instance considers the initial list of `uid`, `groupMembership` and then only allows and releases attributes whose value's length is 3 characters. Therefor, out of the above list, only `groupMembership` is released to the application. + +{% highlight xml %} + + + + + + uid + groupMembership + + + + + + + + +{% endhighlight %} + +### Caching/Updating Attributes +By default, [resolved attributes](Attribute-Resolution.html) are cached to the length of the SSO session. If there are any attribute value changes since the commencement of SSO session, the changes are not reflected and returned back to the service upon release time. + +####Components + +####`PrincipalAttributesRepository` +Parent component that describes the relationship between a CAS `Principal` and the underlying attribute repository source. + +####`DefaultPrincipalAttributesRepository` +The default relationship between a CAS `Principal` and the underlying attribute repository source, such that principal attributes are kept as they are without any additional processes to evaluate and update them. This need not be configured explicitly. + +####`CachingPrincipalAttributesRepository` +The relationship between a CAS `Principal` and the underlying attribute repository source, that describes how and at what length the CAS `Principal` attributes should be cached. Upon attribute release time, this component is consulted to ensure that appropriate attribute values are released to the scoped service, per the cache expiration policy. If the expiration policy has passed, the underlying attribute repository source will be consulted to figure out the available set of attributes. + +The default caching policy is 2 hours which can be controlled via the `cas.attrs.timeToExpireInHours` property. This component also has the ability to resolve conflicts between existing principal attributes and those that are retrieved from repository source via a `mergingStrategy` property. This is useful if you want to preserve the collection of attributes that are already available to the principal that were retrieved from a different place during the authentication event, etc. + +
Caching Upon Release

Note that the policy is only consulted at release time, upon a service ticket validation event. If there are any custom webflows and such that wish to rely on the resolved Principal AND also wish to receive an updated set of attributes, those components must consult the underlying source directory without relying on the Principal.

+ +Sample configuration follows: + +{% highlight xml %} + + + + + + mail + + + + + + + + +{% endhighlight %} + + +####Merging Strategies +By default, no merging strategy takes place, which means the principal attributes are always ignored and attributes from the source are always returned. But any of the following merging strategies may be a suitable option: + +* `MultivaluedAttributeMerger` +Attributes with the same name are merged into multi-valued lists. + +For example: + +1. Principal has attributes `{email=eric.dalquist@example.com, phone=123-456-7890}` +2. Source has attributes `{phone=[111-222-3333, 000-999-8888], office=3233}` +3. The resulting merged would have attributes: `{email=eric.dalquist@example.com, phone=[123-456-7890, 111-222-3333, 000-999-8888], office=3233}` + + +{% highlight xml %} + +... + + + + + + + +... + +{% endhighlight %} + +* `NoncollidingAttributeAdder` +Attributes are merged such that attributes from the source that don't already exist for the principal are produced. + +For example: + +1. Principal has attributes `{email=eric.dalquist@example.com, phone=123-456-7890}` +2. Source has attributes `{phone=[111-222-3333, 000-999-8888], office=3233}` +3. The resulting merged would have attributes: `{email=eric.dalquist@example.com, phone=123-456-7890, office=3233}` + +{% highlight xml %} + +... + + + + + + + +... + +{% endhighlight %} + +* `ReplacingAttributeAdder` +Attributes are merged such that attributes from the source always replace principal attributes. + +For example: + +1. Principal has attributes `{email=eric.dalquist@example.com, phone=123-456-7890}` +2. Source has attributes `{phone=[111-222-3333, 000-999-8888], office=3233}` +3. The resulting merged would have attributes: `{email=eric.dalquist@example.com, phone=[111-222-3333, 000-999-8888], office=3233}` + + +{% highlight xml %} + +... + + + + + + + +... + +{% endhighlight %} + +###Encrypting Attributes +CAS by default supports the ability to encrypt certain attributes, such as the proxy-granting ticket and the credential conditionally. +If you wish to take this a step further and encrypt other attributes that you deem sensitive, you can use the following components +as a baseline to carry out the task at hand: + +`DefaultCasAttributeEncoder` +The default implementation of the attribute encoder that will use a per-service key-pair +to encrypt. It will attempt to query the collection of attributes that resolved to determine +which attributes can be encoded. Attributes will be encoded via a `RegisteredServiceCipherExecutor`. + +{% highlight xml %} + + + + + +{% endhighlight %} diff --git a/cas-server-documentation/integration/Attribute-Resolution.md b/cas-server-documentation/integration/Attribute-Resolution.md new file mode 100644 index 000000000000..c99676df4722 --- /dev/null +++ b/cas-server-documentation/integration/Attribute-Resolution.md @@ -0,0 +1,128 @@ +--- +layout: default +title: CAS - Attribute Resolution +--- + +# Attribute Resolution +Attribute resolution strategies are controlled by the [Person Directory project](https://github.com/Jasig/person-directory‎). The Person Directory dependency is automatically bundled with the CAS server. Therefor, declaring an additional dependency will not be required. This Person Directory project supports both LDAP and JDBC attribute resolution, caching, attribute aggregation from multiple attribute sources, etc. + +
Default Caching Policy

By default, attributes are cached to the length of the SSO session. This means that while the underlying component provided by Person Directory may have a different caching model, attributes by default and from a CAS perspective will not be refreshed and retrieved again on subsequent requests as long as the SSO session exists.

+ + +## Components +A Person Directory `IPersonAttributeDao` attribute source is defined and configured to describe the global set of attributes to be fetched for each authenticated principal. That global set of attributes is then filtered by the service manager according to service-specific attribute release rules. + +### Person Directory + +| Component | Description +|-----------------------------------+--------------------------------------------------------------------------------+ +| `MergingPersonAttributeDaoImpl`| Designed to query multiple `IPersonAttributeDaos` in order and merge the results into a single result set. Merging strategies may be configured via instances of `IAttributeMerger`. +| `CachingPersonAttributeDaoImpl`| Provides the ability to cache results of executed inner DAOs. +| `CascadingPersonAttributeDao`| Designed to query multiple `IPersonAttributeDaos` in order and merge the results into a single result set. As each `IPersonAttributesAttributeDao` is queried the attributes from the first `IPersonAttributes` in the result set are used as the query for the next `IPersonAttributesAttributeDao`. +| `StubPersonAttributeDao`| Backed by a single Map which this implementation will always return, useful for returning static values. +| `MessageFormatPersonAttributeDao`| Provides the ability to create attributes based on other other attribute values as arguments. +| `RegexGatewayPersonAttributeDao`| Conditionally execute an inner DAO if the data in the seed matches criteria set out by the configured patterns. +| `SingleRowJdbcPersonAttributeDao`| The implementation that maps from column names in the result of a SQL query to attribute names. +| `MultiRowJdbcPersonAttributeDao`| Designed to work against a table where there is a mapping of one row to many users. Should be used if the database is structured such that there is a column for attribute names and column(s) for the corresponding values. +| `XmlPersonAttributeDao`| XML backed person attribute DAO that supports wildcard searching. +| `LdapPersonAttributeDao`| Queries an LDAP directory to populate person attributes using Spring Framework. +| `GroovyPersonAttributeDao`| Resolve attributes based on an external groovy script. +| `TomlLdapPersonAttributeDao`| Resolve person attributes and insert the ldap/context settings from an external Toml file. +| `JsonBackedComplexStubPersonAttributeDao`| Resolve person attributes that are specified in an external JSON file. + +More about the Person Directory and its configurable sources [can be found here](https://wiki.jasig.org/display/PDM15/Person+Directory+1.5+Manual). + + +### CAS +The CAS project provides the following additional implementations: + +| Component | Description +|-----------------------------------+--------------------------------------------------------------------------------+ +| `LdapPersonAttributeDao`| Queries an LDAP directory to populate person attributes using the bundled CAS LDAP libraries. + +### Sample Usage + + +####LDAP +The following snippet assumes that connection information beans are already defined. + +{% highlight xml %} + + + + + + + + + + +{% endhighlight %} + + +####JDBC +The following snippet assumes that connection information beans are already defined. + +{% highlight xml %} + + + + + + + + + + + + + + + + + +{% endhighlight %} + + +####Caching, Merging and Cascading +Note that this snippet below strictly uses the Person Directory components for resolving attributes. + +{% highlight xml %} + + + + + + + + + + + + + + + + + + + + + + + + + + + +{% endhighlight %} diff --git a/cas-server-documentation/integration/CAS-Clients.md b/cas-server-documentation/integration/CAS-Clients.md new file mode 100644 index 000000000000..0208a0266957 --- /dev/null +++ b/cas-server-documentation/integration/CAS-Clients.md @@ -0,0 +1,37 @@ +--- +layout: default +title: CAS - CAS Clients +--- + +#Overview +A CAS client is also a software package that can be integrated with various software platforms and applications in order to communicate with the CAS server using or or more supported protocols. CAS clients supporting a number of software platforms and products have been developed. + + +##Official Clients +* [.NET CAS Client](https://github.com/Jasig/dotnet-cas-client) +* [Java CAS Client](https://github.com/Jasig/java-cas-client) +* [PHP CAS Client](https://github.com/Jasig/phpCAS) +* [Apache CAS Client](https://github.com/Jasig/mod_auth_cas) + + +##Other Clients +Other unofficial or incubating CAS clients may be [found here](https://wiki.jasig.org/display/CASC). + + +##Framework Support +The following programming frameworks have built-in support for CAS: + +* [Spring Security](http://static.springsource.org/spring-security/site/) +* [Apache Shiro](http://shiro.apache.org/cas.html) + + + +##Build your own CAS client +As a lot of CAS clients already exist, there is little opportunity to develop a CAS client and it should be avoided as much as possible. Indeed, creating your own client is not an easy job and you're most likely to generate security breaches. + +Though, if you really need to create your own CAS client, please be aware of these incomplete guidelines: + +* Rely on a static internal configuration instead of leveraging the behaviour on received inputs which can be forged +* Ensure that all outside inputs are properly decoded and encoded when used calls to CAS or other services +* Ensure that input is validated and that overly large inputs are discarded. + diff --git a/cas-server-documentation/integration/ClearPass-Proxy-Authentication.md b/cas-server-documentation/integration/ClearPass-Proxy-Authentication.md new file mode 100644 index 000000000000..42baa9236a99 --- /dev/null +++ b/cas-server-documentation/integration/ClearPass-Proxy-Authentication.md @@ -0,0 +1,309 @@ +--- +layout: default +title: CAS - ClearPass +--- + +# ClearPass: Credential Caching and Replay + +
Deprecated!

Exercising ClearPass via the architecture and configuration that is described below is deprecated. Consider [using the alternative](ClearPass.html) that would allow adopters to consume the credential directly in the CAS validation response.

+ +To enable single sign-on into some legacy application it may be necessary to provide them with the actual cleartext password. While such approach inevitably increases security risk, at times this may be a necessary evil in order to integrate applications with CAS. + +
Usage Warning!

ClearPass is turned off by default. No applications will be able to obtain the user credentials unless ClearPass is explicitly turned on by the below configuration.

+ +## Architecture +A service may obtain cleartext credentials for an authenticated user by presenting a valid proxy ticket obtained specifically for the CAS cleartext extension service end-point that is ClearPass. Tickets issued for a ClearPass response are validated in the same way you would access a traditional proxied service. ClearPass ensures this by just being another CAS Client. Credentials are cached inside an Ehcache-backed map with support for encryption of the obtained password in memory. + +Upon receiving the request, ClearPass ensures that the following validation criteria are met: + +* The proxy ticket was obtained for the URL specifying location of the ClearPass service +* The proxy is valid according to standard CAS spec +* The proxy ticket is indeed a proxy ticket, not a service ticket +* Each member of the proxy chain has been given explicit permission to receive cleartext credentials + + +## Validation Responses +Upon successful validation the ClearPass service provides credentials in the following response: + +{% highlight xml %} + + + actual_password + + +{% endhighlight %} + +If the validation fails, the traditional response is a 403 Status code being returned. If there are failures for any other reason other than authorization, the following error response is returned: + +{% highlight xml %} + + description of the problem + +{% endhighlight %} + + +## Components +Support is enabled by including the following dependency in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-extension-clearpass + ${cas.version} + runtime + +{% endhighlight %} + +###`CacheCredentialsMetaDataPopulator` +Retrieve and store the password in our cache. + + +###`ClearPassController` +A controller that returns the password based on some external authentication/authorization rules. Attempts to obtain the password from the cache and render the appropriate output. + + +###`EncryptedMapDecorator` +A `Map` implementation that will hash and store cached credentials. + + +###`EhcacheBackedMap` +A `Map` implementation that will use Ehcache as the backend storage. + + +###`TicketRegistryDecorator` +A ticket registry implementation that dispatches ticketing operations to the *real* registry while mapping tickets to user names and placing them in the provided cache. + + +## Single Node Configuration + + +###`AuthenticationMetaDataPopulator` in `deployerConfigContext.xml` +Uncomment the below element that is responsible for capturing and caching the password: + +{% highlight xml %} + + + + + + +{% endhighlight %} + + +###Modifying `web.xml` +In your Maven overlay, modify the `web.xml` to include the following: +{% highlight xml %} + + cas + /clearPass + +{% endhighlight %} + Be sure to put this snippet with the other servlet-mappings. + +Next, add the following filter and filter-mapping: + +{% highlight xml %} + + clearPassFilterChainProxy + org.springframework.web.filter.DelegatingFilterProxy + + + + clearPassFilterChainProxy + /clearPass + +{% endhighlight %} + +Be sure to put this snippet with the other filter and filter-mappings. + + +###Modifying `clearpass-configuration.xml` +Obtain a copy of the [`clearpass-configuration.xml`](https://github.com/Jasig/cas/blob/master/cas-server-webapp/src/main/webapp/WEB-INF/unused-spring-configuration/clearpass-configuration.xml) file inside the `WEB-INF/unused-spring-configuration` of the project. Place that in your project's `WEB-INF/spring-configuration` directory. + +Next, declare the following bean inside the file: +{% highlight xml %} + + + + https://proxy.server.edu/proxyCallback + ... + + + +{% endhighlight %} + +The above bean defines the list of proxying services authorized to obtain ClearPass credentials. Also note that +proxy urls in the above list have to be fully specified and must produce an exact match. Otherwise, the CAS server +will report back that the proxy chain is invalid for the requesting proxy url. + + +Alternatively, you may replace: + +{% highlight xml %} + +{% endhighlight %} + +with: + +{% highlight xml %} + +{% endhighlight %} + +...to allow all proxying services to be able to obtain ClearPass credentials. + +
Usage Warning!

It's not appropriate in your environment to allow any service that can obtain a proxy ticket to proxy to ClearPass. Explicitly authorizing proxy chains to access ClearPass (and denying all unauthorized proxy chains) is an important access control on release of the end-user's password.

+ + +###Modifying `ticketRegistry.xml` +Obtain a copy of the [`ticketRegistry.xml`](https://github.com/Jasig/cas/blob/master/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/ticketRegistry.xml) file and place that in your project's `WEB-INF/spring-configuration` directory. + +Replace: + +{% highlight xml %} + +{% endhighlight %} + +with: + +{% highlight xml %} + +{% endhighlight %} + + +##Multiple Nodes Configuration +ClearPass stores the password information it collects in a non-distributed EhCache-based Map. This works fine in single-server CAS environments but causes issues in multi-server CAS environments. In a normal multi-server CAS environment you would use a Distributed Ticket Registry like the `MemcacheTicketRegistry` or the `EhcacheTicketRegistry` so that all CAS servers would have knowledge of all the tickets. After the distributed Ticket Registry is setup you should replace ClearPass's default in-memory Map with a Map implemenation that matches your ticket registry. + + +###EhCache-based Map +By default ClearPass is setup to use a non-distrbuted EhCache to store its passwords. If you are using the `EhcacheTicketRegistry` you will want to ensure that your ehcacheClearPass.xml file is setup to replicate the ClearPass Ehcache to all your CAS servers. + + +####Configuration + + +#####Sample `clearpass-configuration.xml` +{% highlight xml %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + clearPassController + + + + +{% endhighlight %} + + +#####Sample `clearpass-replicated.xml` +{% highlight xml %} + + + + + + + + + +{% endhighlight %} + +Note that the above uses manual peer discovery with RMI replication to transfer cached objects that are obtained by ClearPass. The IP addresses need to be changed for each CAS node to point to each other. + + +###Memcached Map +The spymemcached java client includes a Memcached Map implementation called `CacheMap`. + + +####Configuration + + +#####Sample `clearpass-configuration.xml` +{% highlight xml %} + + + + + + + + + + + + + + + + + + + + + + + + + + + +{% endhighlight %} + +Note that if you are using an SSH tunnel for your Memcached connections the Encrypted Map Decorator would not be necessary. diff --git a/cas-server-documentation/integration/ClearPass.md b/cas-server-documentation/integration/ClearPass.md new file mode 100644 index 000000000000..7dc10bcc3560 --- /dev/null +++ b/cas-server-documentation/integration/ClearPass.md @@ -0,0 +1,102 @@ +--- +layout: default +title: CAS - ClearPass +--- + +# ClearPass: Credential Caching and Replay +To enable single sign-on into some legacy application it may be necessary to provide them with the actual password. While such approach inevitably increases security risk, at times this may be a necessary evil in order to integrate applications with CAS. + +
Usage Warning!

ClearPass is turned off by default. No applications will be able to obtain the user credentials unless ClearPass is explicitly turned on by the below configuration.

+ +
ClearPass via Proxying!

If you wish to review the configuration for ClearPass via proxying, please see this link instead.

+ +## Architecture +CAS is able to issue the credential password directly in the CAS validation response. This previously was handled via a proxy authentication sequence and obtaining a proxy-granting ticket for the ClearPass service and was necessary in order to establish trust between the client application and the CAS server. This document describes the configuration that can be applied in order to receive the credential password as an attribute in the CAS validation response. + +In order to successfully establish trust between the +CAS server and the application, private/public key pairs are generated by the client application and then **the public key** distributed and configured inside CAS. CAS will use the public key to encrypt the credential password and will issue a new attribute `` in the validation response, only if the service is authorized to receive it. + +Note that the return of the credential is only carried out by the CAS validation response, provided the client +application issues a request to the `/p3/serviceValidate` endpoint (or `/p3/proxyValidate`). Other means of returning attributes to CAS, such as SAML1 will **not** support the additional returning of this value. + +##Configuration + +###Register Service Public Key +Once you have received the public key from the client application owner, it must be first registered inside the CAS server's service registry: + +{% highlight xml %} +... + + + +... +{% endhighlight %} + +###Authorize Credential for Service +The service that holds the public key above must also be authorized to receive the password +as an attribute for the given attribute release policy of choice: + +{% highlight xml %} +... + + + +... +{% endhighlight %} + +###Decrypt the Password +Once the client application has received the `credential` attribute in the CAS validation response, it can decrypt it via its own private key. Since the attribute is base64 encoded by default, it needs to be decoded first before +decryption can occur. Here's a sample code snippet: + +{% highlight java %} + +final Map attributes = ... +final String encodedPsw = (String) attributes.get("credential"); +final PrivateKey privateKey = ... +final Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm()); +final byte[] cred64 = decodeBase64ToByteArray(encodedPsw); +cipher.init(Cipher.DECRYPT_MODE, privateKey); +final byte[] cipherData = cipher.doFinal(cred64); +return new String(cipherData); + +{% endhighlight %} + + +##Components + +- `RegisteredServiceCipherExecutor` +Defines how to encrypt data based on registered service's public key, etc. + +- `DefaultRegisteredServiceCipherExecutor` +A default implementation of the `RegisteredServiceCipherExecutor` +that will use the service's public key to initialize the cipher to +encrypt and encode the value. All results are converted to base-64. + +- `CasAttributeEncoder` +Parent component that defines how a CAS attribute +is to be encoded and signed in the CAS validation response. + +- `DefaultCasAttributeEncoder` +The default implementation of the attribute encoder that will use a per-service key-pair +to encrypt. It will attempt to query the collection of attributes that resolved to determine +which attributes can be encoded. Attributes will be encoded via a `RegisteredServiceCipherExecutor`. + +{% highlight xml %} + + + + + +{% endhighlight %} diff --git a/cas-server-documentation/integration/Delegate-Authentication.md b/cas-server-documentation/integration/Delegate-Authentication.md new file mode 100644 index 000000000000..bbebacdad620 --- /dev/null +++ b/cas-server-documentation/integration/Delegate-Authentication.md @@ -0,0 +1,293 @@ +--- +layout: default +title: CAS - Delegate authentication +--- + +#Overview +The CAS server implements the CAS protocol on server side and may even behave like an OAuth provider, an OpenID provider or a SAML IdP. Whatever the protocol, the CAS server is first of all a server. + +But the CAS server can also act as a client using the [pac4j library](https://github.com/leleuj/pac4j) and delegate the authentication to: + +* Another CAS server +* An OAuth provider: Facebook, Twitter, Google, LinkedIn, Yahoo and several other providers +* An OpenID provider: myopenid.com +* A SAML identity provider +* An OpenID Connect identity provider. + +Support is enabled by including the following dependency in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-support-pac4j + ${cas.version} + +{% endhighlight %} + +##How to use CAS/OAuth/OpenID/SAML client support in CAS applications? + + +###Information returned by a delegated authentication + +Once you have configured (see information below) your CAS server to act as an OAuth, CAS, OpenID (Connect) or SAML client, users will be able to authenticate at a OAuth/CAS/OpenID/SAML provider (like Facebook) instead of authenticating directly inside the CAS server. + +In the CAS server, after this kind of delegated authentication, users have specific authentication data. + +The `Authentication` object has: + +* The attribute `AuthenticationManager.AUTHENTICATION_METHOD_ATTRIBUTE` (authenticationMethod) set to *`org.jasig.cas.support.pac4j.authentication.handler.support.ClientAuthenticationHandler`* +* The attribute *`clientName`* set to the type of the provider used during authentication process. + +The `Principal` object of the `Authentication` object has: + +* An identifier which is the profile type + `#` + the identifier of the user for this provider (i.e `FacebookProfile#0000000001`) +* Attributes populated by the data retrieved from the provider (first name, last name, birthdate...) + +###How to send profile attributes to CAS client applications? + +In CAS applications, through service ticket validation, user information are pushed to the CAS client and therefore to the application itself. + +The identifier of the user is always pushed to the CAS client. For user attributes, it involves both the configuration at the server and the way of validating service tickets. + +On CAS server side, to push attributes to the CAS client, it should be configured in the `deployerConfigContext.xml` file for the expected service: + +{% highlight xml %} + + + + + + + + + + + + + name + first_name + middle_name +... +{% endhighlight %} + +On CAS client side, to receive attributes, you need to use the SAML validation or the CAS 3.0 validation, that is the `/p3/serviceValidate` url. + +###How to recreate user profiles in CAS applications? + +In the CAS server, the complete user profile is known but when attributes are sent back to the CAS client applications, there is some kind of "CAS serialization" which makes data uneasy to be restored at their original state. + +Though, you can now completely rebuild the original user profile from data returned in the CAS `Assertion`. + +After validating the service ticket, an `Assertion` is available in the CAS client from which you can get the identifier and the attributes of the authenticated user using the pac4j library: + +{% highlight java %} +final AttributePrincipal principal = assertion.getPrincipal(); +final String id = principal.getName(); +final Map attributes = principal.getAttributes(); +{% endhighlight %} + +As the identifier stores the kind of profile in its own definition (`*clientName#idAtProvider*`), you can use the `org.pac4j.core.profile.ProfileHelper.buildProfile(id, attributes)` method to recreate the original profile: + +{% highlight java %} +final FacebookProfile rebuiltProfileOnCasClientSide = + (FacebookProfile) ProfileHelper.buildProfile(id, attributes); +{% endhighlight %} + +and then use it in your application! + +##Configuration + +###Add the required pac4j-* libraries + +To add CAS client support, add the following dependency: + +{% highlight xml %} + + org.pac4j + pac4j-cas + ${pac4j.version} + +{% endhighlight %} + +To add OAuth client support, add the following dependency: + +{% highlight xml %} + + org.pac4j + pac4j-oauth + ${pac4j.version} + +{% endhighlight %} + +To add OpenID client support, add the following dependency: + +{% highlight xml %} + + org.pac4j + pac4j-openid + ${pac4j.version} + +{% endhighlight %} + +To add OpenID Connect client support, add the following dependency: + +{% highlight xml %} + + org.pac4j + pac4j-oidc + ${pac4j.version} + +{% endhighlight %} + +To add SAML support, add the following dependency: + +{% highlight xml %} + + org.pac4j + pac4j-saml + ${pac4j.version} + +{% endhighlight %} + +###Add the needed clients + +A provider is a server which can authenticate user (like Google, Yahoo...) instead of a CAS server. If you want to delegate the CAS authentication to Twitter for example, you have to add an OAuth client for the provider: Twitter. Clients classes are defined in the pac4j library. + +All the needed clients to authenticate against providers must be declared in the `applicationContext.xml` file: + +{% highlight xml %} + + + + + + + + + + + + + + + + + + + + + + + +{% endhighlight %} + +For each OAuth provider, the CAS server is considered as an OAuth client and therefore should be declared also at the OAuth provider. After declaration, a key and a secret is given by the OAuth provider which has to be defined in the beans (*the_key_for_xxx* and *the_secret_for_xxx* values for the *key* and *secret* properties). + +For the CAS OAuth wrapping, the *casOAuthUrl* property must be set to the OAuth wrapping url of the other CAS server which is using OAuth wrapping (something like *http://mycasserver2/oauth2.0*). + +To simplify configuration, all clients and the CAS server login url are gathered in the same `Clients` configuration bean (in the `applicationContext.xml` file): + +{% highlight xml %} + + + + + + + + + + + + +{% endhighlight %} + + +###Add the client action in webflow + +In the `login-webflow.xml` file, the `ClientAction` must be added at the beginning of the webflow. Its role is to intercept callback calls from providers (like Facebook, Twitter...) after a delegated authentication: + +{% highlight xml %} + + + + + + + +{% endhighlight %} + +This `ClientAction` has to be defined in the `cas-servlet.xml` file with all the needed clients: + +{% highlight xml %} + +{% endhighlight %} + +This `ClientAction` uses the *centralAuthenticationService* bean to finish the CAS authentication and references all the clients. + +###Add the handler and the metadata populator (optional) for authentication + +To be able to finish authenticating users in the CAS server after a remote authentication by an external provider, you have to add the `ClientAuthenticationHandler` class and might add the `ClientAuthenticationMetaDataPopulator` class (to track the provider) in the `deployerConfigContext.xml` file: + +{% highlight xml %} + + + + + + + + + + + + + + + + + + +{% endhighlight %} + +By default, the identifier returned by a delegated authentication is composed of the profile name and the technical identifier of the provider, like `FacebookProfile#1234`, to ensure the identifier uniqueness. Though, you can remove this behaviour and only return the technical identifier by using: + +{% highlight xml %} + +{% endhighlight %} + + +###Add links on the login page to authenticate on remote providers + +To start authentication on a remote provider, these links must be added on the login page `casLoginView.jsp` (*ClientNameUrl* attributes are automatically created by the `ClientAction`): + +{% highlight html %} +Authenticate with Facebook
+
+Authenticate with Twitter
+
+Authenticate with another CAS server using OAuth v2.0 protocol
+
+Authenticate with another CAS server using CAS protocol
+
+ +

Authenticate with MyOpenId.com

+
+ + +
+{% endhighlight %} + +##Demo + +Take a look at this demo: [cas-pac4j-oauth-demo](https://github.com/leleuj/cas-pac4j-oauth-demo) to see this authentication delegation mechanism in action. + diff --git a/cas-server-documentation/integration/Google-Apps-Integration.md b/cas-server-documentation/integration/Google-Apps-Integration.md new file mode 100644 index 000000000000..02e8a4dd89ba --- /dev/null +++ b/cas-server-documentation/integration/Google-Apps-Integration.md @@ -0,0 +1,74 @@ +--- +layout: default +title: CAS - Google Apps Integration +--- + +#Overview +Google Apps for Education (or any of the Google Apps) utilizes SAML 2.0 to provide an integration point for external authentication services. SAML2 support for Google Apps integration is built via a special `ArgumentExtractor` and accompanying `Service` to provide process and understand SAML 2.0 requests from Google. + +Support is enabled by including the following dependency in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-support-saml + ${cas.version} + +{% endhighlight %} + +##Generate Public/Private Keys +The first step is to generate DSA/RSA public and private keys. These are used to sign and read the Assertions. After keys are created, the public key needs to be registered with Google. + +The keys will also need to be available to the CAS application (but not publicly available over the Internet) via the classpath (i.e. `WEB-INF/classes`) though any location accessible by the user running the web server instance and not served publicly to the Internet is acceptable. Thus, inside `WEB-INF` is nice because `WEB-INF` is scoped to the web application but not normally served. `/etc/cas/keys/` is also fine as well and protects the key from being overwritten on deploy of a new CAS webapp version. + +{% highlight bash %} +openssl genrsa -out private.key 1024 +openssl rsa -pubout -in private.key -out public.key -inform PEM -outform DER +openssl pkcs8 -topk8 -inform PER -outform DER -nocrypt -in private.key -out private.p8 +openssl req -new -x509 -key private.key -out x509.pem -days 365 +{% endhighlight %} + +The `public.key` and `private.p8` go into classpath. The `x509.pem` file should be uploaded into Google Apps under Security/SSO. + +##Configure CAS +Google Accounts integration within CAS is enabled by simply adding an additional `ArgumentExtractor`. `WEB-INF/spring-configuration/argumentExtractorsConfiguration.xml` should be modified to add the following: + +{% highlight xml %} + + + + + +{% endhighlight %} + +Replace the public.key and private.key with the names of your key files. If they are not available on the classpath, change the location to point to the location of the keys. If you are using DSA instead of RSA, change the algorithm as appropriate. + +Also, ensure that Google Apps is registered in your Service Registry, by the `serviceId`: `https://www.google.com/a/YourGoogleDomain/acs` + +##Configure Username Attribute +As an optional step, you can configure an `alternateUsername` to be send to Google in the SAML reply. This `alternameUsername` can be mapped in XML to any CAS principal attribute. + +{% highlight xml %} + +{% endhighlight %} + +##Configure Google +You'll need to provide Google with the URL for your SAML-based SSO service, as well as the URL your users will be redirected to when they log out of a hosted Google application. +Use the following URLs when you are configuring for Google Apps: + +* Sign-in page URL: `https://sso.school.edu/cas/login` +* Sign-out page URL: `https://sso.school.edu/cas/logout` +* Change password URL: `https://mgmt.password.edu/` + +##Test +Attempt to access a Google-hosted application, such as Google Calendar with the url: `http://calendar.google.com/a/YourGoogleDomain` diff --git a/cas-server-documentation/integration/Shibboleth.md b/cas-server-documentation/integration/Shibboleth.md new file mode 100644 index 000000000000..da36107db90d --- /dev/null +++ b/cas-server-documentation/integration/Shibboleth.md @@ -0,0 +1,278 @@ +--- +layout: default +title: CAS - Shibboleth Integration +--- + +#Overview +CAS can be integrated with the [Shibboleth federated SSO platform](http://shibboleth.net/) by a couple different strategies. It is possible to designate CAS to serve as the authentication provider for the Shibboleth IdP. With such a setup, when user is routed to the IdP, the following may take place: + +- If the user has already authenticated to CAS and has a valid CAS SSO session, the IdP will transparently perform the requested action, e.g. attribute release. +- If the user does not have a valid CAS SSO session, the user will be redirected to CAS and must authenticate before the IDP proceeds with the requested action. + + +##SSO for Shibboleth IdP (RemoteUser) + +###Configuration + +####Include CAS Client Libraries in IdP Deployable + +Download the latest Java CAS Client Release and modify the IdP war deployable such that the following jars are included in the `./lib` installer folder, then redeploy the Idp with these files: + +{% highlight bash %} +cas-client-$VERSION/modules/cas-client-core-$VERSION.jar +{% endhighlight %} + +####Modify `$SHIB_HOME/conf/handler.xml` + +Define the `RemoteUser` authentication method to be used with CAS authentication. +{% highlight xml %} + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified + + + urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + + +{% endhighlight %} + + +####Modify IdP Deployable `web.xml` +Add the following XML blocks to the `web.xml` file for the IdP war deployable. +{% highlight xml %} + + + serverName + ${idp.hostname} + +CAS Filters + + + CAS Authentication Filter + + org.jasig.cas.client.authentication.AuthenticationFilter + + + casServerLoginUrl + ${cas.server.url}login + + + + + CAS Authentication Filter + /Authn/RemoteUser + + + + CAS Validation Filter + + org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter + + + casServerUrlPrefix + ${cas.server.url} + + + redirectAfterValidation + true + + + + + CAS Validation Filter + /Authn/RemoteUser + + + + CAS HttpServletRequest Wrapper Filter + + org.jasig.cas.client.util.HttpServletRequestWrapperFilter + + + + + CAS HttpServletRequest Wrapper Filter + /Authn/RemoteUser + +{% endhighlight %} + + +####Enable `RemoteUserHandler` in Idp Deployable `web.xml` +Ensure the following is defined: + +{% highlight xml %} + + + RemoteUserAuthHandler + edu.internet2.middleware.shibboleth.idp.authn.provider.RemoteUserAuthServlet + + + + RemoteUserAuthHandler + /Authn/RemoteUser + +{% endhighlight %} + + +##SSO for Shibboleth IdP (External) +This is a Shibboleth IdP external authentication plugin that delegates the authentication to CAS. The advantage of using this component over the plain `RemoteUser` solution is the ability to utilize a full range of native CAS protocol features such as `renew` and `gateway`. + +The plugin is available for both Shibboleth Identity Provider [v2](https://github.com/Unicon/shib-cas-authn2) and [v3](https://github.com/Unicon/shib-cas-authn3). + +###Relying Party EntityId +The authentication plugin is able to pass the relying party's entity ID over to the CAS server upon authentication requests. The entity ID is passed in form of a url parameter to the CAS server as such: + +``` +https://sso.example.org/cas/login?service=&entityId= +``` + +###Displaying SAML MDUI +The CAS server is able to recognize the `entityId` parameter and display SAML MDUI on the login page, +that is provided by the metadata associated with the relying party. This means that CAS will also need to know +about metadata sources that the identity provider uses. + +###Configuration + +Support is enabled by including the following dependency in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-support-saml + ${cas.version} + +{% endhighlight %} + +Then, adjust `cas-servlet.xml` with the following: + +{% highlight xml %} + +{% endhighlight %} + +Metadata sources in the CAS server can be configured via the following ways: + +#### Static +In this case, metadata sources are statically provided via classpath, file or url resources. + +{% highlight xml %} + + + + + + + + + + + +{% endhighlight %} + +#### Dynamic +In this case, metadata sources are provided via the +[Metadata Query Protocol](https://spaces.internet2.edu/display/InCFederation/Metadata+Query+Protocol), which +is a REST-like API for requesting and receiving arbitrary metadata. CAS will contact +the metadata server to query for the metadata based on the `entityId` provided. + +{% highlight xml %} + + + + + + + + + + + +{% endhighlight %} + +####Configure Metadata Filters +Metadata filters can be configured to validate and verify the received +metadata in both scenarios. Filters typically check for validity of signaures, +whether `validUntil` exists, etc. The following example attempts to validate +the signature on the metadata via a pre-configured public key: + +{% highlight xml %} + + + + + + + + + + + + + + + + + + + + + + + + + + +{% endhighlight %} + +You will need to modify your metadata retrieval process, whether static or dynamic, +to adjust for the appropriate metadata filter if need be. + +###Display MDUI +Modify the `login-webflow.xml` to execute the `SamlMetadataUIParserAction` action +when the login form is rendered: + +{% highlight xml %} + + ... + + ... + + ... + + ... + +{% endhighlight %} + +A sample screenshot of the above configuration in action: + +![capture](https://cloud.githubusercontent.com/assets/1205228/8120071/095c7628-1050-11e5-810e-7bce128391df.PNG) + +##Shibboleth Service Provider Proxy +The [CASShib project](https://code.google.com/p/casshib/) "Shibbolizes" the CAS server and enables end applications to get authentication information from CAS rather than the Shibboleth Service Provider. CASShib is designed as an alternative to deploying the Shibboleth service provider for each application in order to: + +- Leverage Shibboleth's sophisticated attribute release policy functionality to enable attribute releasing to services in the local environment. +- Offer the chance for local applications to easily become federated services. diff --git a/cas-server-documentation/planning/Architecture.md b/cas-server-documentation/planning/Architecture.md new file mode 100644 index 000000000000..3c5ea9da9cac --- /dev/null +++ b/cas-server-documentation/planning/Architecture.md @@ -0,0 +1,76 @@ +--- +layout: default +title: CAS - Architecture +--- + +# Architecture + +![CAS Architecture Diagram](../images/cas_architecture.png "CAS Architecture Diagram") + +## System Components +The CAS server and clients comprise the two physical components of the CAS system architecture that communicate +by means of various protocols. + + +### CAS Server +The CAS server is Java servlet built on the Spring Framework whose primary responsibility is to authenticate users +and grant access to CAS-enabled services, commonly called CAS clients, by issuing and validating tickets. +An SSO session is created when the server issues a ticket-granting ticket (TGT) to the user upon successful login. +A service ticket (ST) is issued to a service at the user's request via browser redirects using the TGT as a token. +The ST is subsequently validated at the CAS server via back-channel communication. +These interactions are described in great detail in the CAS Protocol document. + + +### CAS Clients +The term "CAS client" has two distinct meanings in its common use. A CAS client is any CAS-enabled application that +can communicate with the server via a supported protocol. A CAS client is also a software package that can be +integrated with various software platforms and applications in order to communicate with the CAS server via some +authentication protocol (e.g. CAS, SAML, OAuth). CAS clients supporting a number of software platforms and products +have been developed. + +Platforms: + +* Apache httpd Server ([mod_auth_cas module](https://wiki.jasig.org/display/CASC/mod_auth_cas)) +* Java ([Java CAS Client](https://wiki.jasig.org/display/CASC/CAS+Client+for+Java+3.1)) +* .NET ([.NET CAS Client](https://wiki.jasig.org/display/CASC/.Net+Cas+Client)) +* PHP ([phpCAS](https://wiki.jasig.org/display/CASC/phpCAS)) +* Perl (PerlCAS) +* Python (pycas) +* Ruby (rubycas-client) + +Applications: + +* Outlook Web Application (ClearPass + .NET CAS Client) +* Atlassian Confluence +* Atlassian JIRA +* Drupal +* Liferay +* uPortal + +When the term "CAS client" appears in this manual without further qualification, it refers to the integration +components such as the Jasig Java CAS Client rather than to the application relying upon (a client of) the CAS server. + + +## Protocols +Clients communicate with the server by any of several supported protocols. All the supported protocols are +conceptually similar, yet some have features or characteristics that make them desirable for particular applications or use cases. For example, the CAS protocol supports delegated (proxy) authentication, and the SAML protocol supports attribute release and single sign-out. + +Supported protocols: + +* [CAS (versions 1, 2, and 3)](../protocol/CAS-Protocol.html) +* [SAML 1.1](../protocol/SAML-Protocol.html) +* [OpenID](../protocol/OpenID-Protocol.html) +* [OAuth (1.0, 2.0)](../protocol/OAuth-Protocol.html) + + +## Software Components +It is helpful to describe the CAS server in terms of three layered subsystems: + +* Web (Spring MVC/Spring Webflow) +* [Ticketing](../installation/Configuring-Ticketing-Components.html) +* [Authentication](../installation/Configuring-Authentication-Components.html) + +Almost all deployment considerations and component configuration involve those three subsystems. The Web tier is the endpoint for communication with all external systems including CAS clients. The Web tier delegates to the ticketing subsystem to generate tickets for CAS client access. The SSO session begins with the issuance of a ticket-granting ticket on successful authentication, thus the ticketing subsystem frequently delegates to the authentication subsystem. + +The authentication system is typically only processing requests at the start of the SSO session, though there are other cases when it can be invoked (e.g. forced authentication). + diff --git a/cas-server-documentation/planning/High-Availability-Guide.md b/cas-server-documentation/planning/High-Availability-Guide.md new file mode 100644 index 000000000000..0bd00b37aadb --- /dev/null +++ b/cas-server-documentation/planning/High-Availability-Guide.md @@ -0,0 +1,177 @@ +--- +layout: default +title: CAS - High Availability Guide +--- + +# High Availability Guide (HA/Clustering) + +A highly available CAS deployment is one that offers resilience in response to various failure modes such that CAS +continues to offer SSO services despite failures. We offer a recommended architecture that provides a starting point for planning and executing a CAS deployment that meets institutional performance and availability requirements. It also provides a framework for understanding CAS software component requirements imposed by HA considerations. + +A high availability (HA) configuration of CAS is achieved by ensuring there is adequate redundancy so that the service is robust in the face of component failures and that routine maintenance can be done without service downtime. This can be achieved with multi-node and to a lesser degree with single-node CAS with advanced virtual machine capabilities. This document will focus on the CAS Server components required to achieve HA. A more quantitative analysis of HA configuration depends on supporting infrastructure and services and is beyond the scope of this document. + +The CAS Server software has had a great track record of being extremely reliable. However, the CAS Server is only a small part of software and hardware that authentication has to traverse to work smoothly. Clustering has typically been used by deployers not only for load handling but also for fail-over. Even if a failure does not occur, it is sometimes desirable to restart a server. For example, if a serious security fix at the operating system level was installed, the server should be restarted immediately. In a cluster of CAS servers, this could be easily accomplished with a rolling restart even during the busiest time. + +Operating a single server traditionally would delay such a restart until a less busy time, while running with a known vulnerability. However, more recently with the growing acceptance of virtual machine technology and its inherent redundancy and fault tolerance, single node CAS has been able to achieve similar qualities. + + +## Recommended Architecture +The following diagram highlights the vital aspects of a highly available CAS deployment. + +![Recommended HA Architecture](../images/recommended_ha_architecture.png "Recommended HA Architecture") + +It's worth pointing out some important characteristics of this architecture: + +* Dependent systems can tolerate up to N-1 node failures. (Where N is the total number of nodes.) +* CAS itself can tolerate up to N-1 node failures. +* Loss of a cache node DOES NOT cause loss of SSO state data (i.e. tickets) in replicating caches. +* Loss of a cache node MAY cause loss of SSO state data in non-replicating caches (e.g. memcached). +* Loss of SSO state data is always graceful: users simply reauthenticate. + +Before proceeding into a detailed discussion of various aspects of the recommended architecture, we offer a guiding +principle for planning a highly available deployment: + +
Aim for Simplicity

Design the simplest solution that meets performance and availability requirements.

+ +Experience has shown that simplicity is a vital system characteristic of successful and robust HA deployments. +Strive for simplicity and you will be well served. + + +## Deployment Scenarios + +###Single-node CAS, HA VM Infrastructure +High availability can be achieved by implementing a single-node CAS running in a sophisticated virtualized environment. This approach to high availability is attractive in the sense that it simplifies the CAS server configuration but requires hardware virtualization technology that may not be present and available. + +####Physical Architecture +In a single-node VM architecture, the CAS server, along with the necessary prerequisites and software dependencies is deployed in a single host VM. +Under this deployment scenario the default in-memory Ticket Registry is sufficient and no Servlet Session replication is required. This simplifies the deployment configuration and is the recommended approach if the VM infrastructure is sufficient to meet HA and scalability needs. + +####Robustness +Hardware component failure/recovery is a feature of the virtualized environment such that the loss of a CPU, memory or power does not cause a failure of the CAS server. + +####Zero downtime maintenance approach +True zero downtime maintenance (i.e. no observable impact to end users) is not achievable with this configuration. However, staging of maintenance and upgrades can be done without downtime by leveraging the cloning ability of most VM infrastructures. Once the new CAS Server node is ready, a brief cutover can be implemented which will effectively end all current SSO sessions. This could be done by scheduling restart of Tomcat during low traffic times, after the new cas.war has been deployed. + +####Scalability +CAS itself has modest computing requirements such that any modern enterprise class server hardware is going to be sufficient to handle 10,000s of users in typical deployment scenarios. In a recent client engagement load testing a single node deployment yielded good results with CAS handling 200 concurrent users at 61 requests per second which roughly translates into 108,000 authentication transactions per hour. These number are of course representative and any benchmark will be highly dependent on local infrastructure. +VM environments should be able to scale the available CPU and memory to meet a wide range of needs. + + +### Multiple CAS Server Nodes +A highly available CAS deployment is composed of two or more nodes behind a hardware load balancer in either active/passive or active/active mode. In general the former offers simplicity with adequate failover; the latter, improved resource usage and reduced service interruptions at the cost of additional complexity. Active-passive configuration can be done with manual or automatic failover in the case where the primary CAS node fails. Active-active configuration is possible with a clustered ticket registry state such that any available CAS node can service any request for the CAS server. [A number of options are available](../installation/Configuring-Ticketing-Components.html) for implementing an active-active configuration with shared ticket state. + +HA can be achieved by implementing a multi-node CAS deployment running on multiple VMs or physical hosts. This approach is attractive since it allows true zero down-time maintenance of the service at the cost of a marginal increase in deployment complexity. + +Multi-node CAS generally involves the following: + +* Installing multiple instances of the CAS server (so that one or more of the servers can be destroyed without the CAS service becoming unavailable) +* Configuring the multiple instances of the CAS server to share ticket state (so that regardless of which CAS server a user or service interacts with, the response from each CAS server is the same.) +* Configuring a solution for directing traffic among the clustered CAS servers, for detecting component failure and removing failed components from service +* Optionally, configuring a solution for sharing session state and session failover across the CAS instances (this isn't typically appropriate, since end-user CAS sessions tend to be short lived and the experience is more request-response style than it is session oriented) - favor short-lived sticky (aka persistent sessions) load-balancing instead (could be a problem with large NAT deployments) +* Having appropriate contingency plans such that the desired margin of headroom against failure is restored when it is exercised. (For example, having three CAS server instances, clustered, serving a load that can be serviced with just two instances.) + + +####Physical Architecture +The physical architecture may be realized through VMs or physical hardware. It is important to note that in a shared ticket state model (Active/Active mode), CAS server nodes need to be able to communicate tickets state across all nodes and as such, firewall restrictions between such nodes needs to be relaxed enough to allow for ticket state replication. + +The service endpoint is a virtual IP address configured at the load balancer. Thus all requests are handled by the load balancer and then routed to available CAS nodes. + + +####Robustness +In the event of a CAS node failure, the work load and authentication requests can properly be rerouted to another CAS node. It is possible that through the failover scenario, some state may be lost depending on where the user is in the login flow and as such, once the rerouting of the request has landed from the failed node to the clone, users may need be presented with the CAS login screen again. This failure mode can be eliminated with Servlet session state replication. + + +####Zero downtime maintenance approach +Maintenance work, such that it would include upgrades and application of patches to the software may be carried out via two general approaches: + +1. In active-passive models, work may be carried out offline on the passive CAS node. The load balancer is then tweaked to switch over the prepared node once ready thereby switching the active-passive nodes around. This results in all CAS SSO sessions being reset and possibly some Ticket validation failures if done during times with high utilization. See below for more details on this approach. + +2. In active-active models, one node can be taken offline while at least one other CAS server node remains alive to respond to requests. Once the upgrade procedure is done, the server can return to the pool while obtaining the ticket state from other active nodes. Certain distributed ticket registry models have the ability to bootstrap themselves by receiving ticket data from other nodes without any manual configuration or adjustment. See below for more details on this approach. + + +####Scalability +Scalability is simply achieved by adding new CAS nodes to the cluster. + + +#### Active/Passive Mode +In an active/passive load balanced configuration, 1 of N nodes serves all requests at any given time. This simplifies ticket storage requirements since it is not necessary to share ticket state among several application nodes. + +In particular, the `DefaultTicketRegistry` component that stores tickets in memory is suitable for active/failover +setups with the understanding that a node failure would result in ticket loss. It's worth repeating that ticket loss results in graceful application failure where users simply reauthenticate to CAS to create new SSO sessions; +CAS client sessions created under prevous SSO sessions would suffer no iterruption or loss of data. + + +#### Active/Active Mode +A load balancer in active/active mode serves requests to all N nodes similutaneously. The load balancer chooses a node to serve a request based on a configured algorithm; typically least active or round robin. In this system architecture, it is vitally important to use a ticket store where a ticket can be located regardless of which CAS node requests it. + +It's instructive to discuss the origin of this requirement. There are two interactions for tickets that occur from +fundamentally different network sources: + +1. User's Web browser contacts CAS to generate a ticket. +2. Target service contacts CAS with a ticket to validate it. + +Since both requests flow through the load balancer from different source addresses, it is not possible to guarantee +that both requests are serviced by the same CAS node. Thus the requirement that a ticket be locatable regardless of +the CAS node that requests it. It should be clear why in-memory storage is not suitable for active/active deployments. + +The active-active architecture allows for a zero down-time transitions between CAS server versions at the time of upgrades. One CAS node instance can be taken offline, undergo maintenance, and then be put back into the production. The same strategy is then repeated for all other CAS nodes. + +There is a further consideration for active/active deployments: session affinity. Session affinity is a feature of +most load balancer equipment where the device performs state management for incoming requests and routes a client to the same node for subsequent requests for a period of time. This feature is recommended and required to avoid servlet container session replication, which is generally more complex and less reliable. The core of this requirement is that servlet container session storage is used to maintain state for the CAS login and logout Webflows. While it is possible to achieve truly stateless active/active deployments by plugging in +[client-based state management](https://github.com/serac/spring-webflow-client-repo) components, such configurations at present have not been proven and are not recommended without careful planning and testing. + + +#### Avoid Round Robin DNS +We _strongly_ recommend avoiding round robin DNS as a cost-effective alternative to a hardware load balancer. +Client cache expiration policy is entirely uncontrollable, and typical cache expiration times are much longer than +desirable periods for node failover. A [reverse proxy](http://httpd.apache.org/docs/current/mod/mod_proxy.html) or +[software load balancer](http://www.linuxvirtualserver.org/software/ipvs.html) are recommended alternatives to hardware. + + +### Cache-Based Ticket Registry +The following cache-based ticket storage components provide the best tradeoff among ease of use, scalability, and +fault tolerance and are suitable for both active/passive and active/active setups: + +* [Hazelcast](../installation/Hazelcast-Ticket-Registry.html) +* [EhCache](../installation/Ehcache-Ticket-Registry.html) +* [JBoss](../installation/JBoss-Cache-Ticket-Registry.html) +* [MemCached](../installation/Memcached-Ticket-Registry.html) + +The particular choice of caching technology should be driven by infrastructure and expertise as much as performance +and availability considerations. It's hardly valuable to have a high-performance cache for which you lack the +expertise to troubleshoot when problems invariably arise. + +The technology considerations of the various cache components merit some discussion since there are notable +differences that impact availability and performance characteristics. Cache systems like Ehcache and JBoss Cache +(and its offspring, Infinispan) offer a distributed cache that presents a single, consistent view of entries regardless +of the node contacted. Distributed caches rely on replication to provide for consistency. Cache systems like memcached +store the ticket on exactly 1 node and use a deterministic algorithm to locate the node containing the ticket: + + N' = f(h(T), N1, N2, N3, ... Nm) + +where _h(T)_ is the hash of the ticket ID, _N1 ... Nm_ is the set of cache nodes, and _N'_ is member of _N ... Nm_. + +These sorts of cache systems do not require replication and generally provide for simplicity at the expense of some +durability. + + +### Distributing Service Definitions +In an HA environment, service definitions must be replicated and accessible by all nodes in the CAS cluster. Typically, this may be achieved by leveraging centralized registry implementation that are backed by JPA or LDAP. Registries that are backed by the file system need to devise a process of ensuring proper file replication, either manually or via background daemon. + + +### Connection Pooling +We _strongly_ recommend that all IO connections to a back-end data stores, such as LDAP directories and databases, +leverage connection pooling where possible. It makes the best use of computational (especially for SSL/TLS connections) and IO resources while providing the best performance characteristics. + + +###Monitoring +CAS adopters typically implement monitoring of the availability of the CAS service using the tools already in use in operational practice for monitoring other enterprise web applications. CAS introduces a new modest monitoring page with authentication by default by the remote_address of the requestor. + + +###Channel Confidentiality +Channel Confidentiality (via SSL/TLS) is assumed and critical to the security posture of the CAS system. This includes both front-channel (between user browser-agent and CAS server) and back-channel (between web application and CAS server) https traffic, any intermediate proxy traffic between load balancers or content filters and CAS nodes, as well as primary authentication (e.g. LDAPS) and attribute resolution (JDBC over SSL). Any break in the privacy controls at any stage comprises the overall security of the system. + + +###Upgrade/patches/security releases +CAS server upgrades should be carried out through the recommended Maven overlay approach. Established as a best practice, the CAS maven overlay approach allows one to seamlessly obtain the intended CAS server version from well known and public repositories while laying custom changes specific on top of the downloaded binary artifact. +In the specifics of the Maven overlay approach, it may also be desirable to externalize the configuration outside of the `cas.war` so that the properties and logging configuration can vary across tiers for the same `cas.war` file. That is, externalizing the environment-specific configuration allows the same `cas.war` to be promoted from server to server and tier to tier, which increases the confidence that the web application that was tested and verified out of production will behave as tested in production. diff --git a/cas-server-documentation/planning/Installation-Requirements.md b/cas-server-documentation/planning/Installation-Requirements.md new file mode 100644 index 000000000000..fa156d3d755e --- /dev/null +++ b/cas-server-documentation/planning/Installation-Requirements.md @@ -0,0 +1,67 @@ +--- +layout: default +title: CAS - Installation Requirements +--- + +# Installation Requirements + +Requirements at a glance: + +1. [Java](http://www.java.com) >=1.7 +2. [Servlet container](http://tomcat.apache.org/) supporting servlet specification >=2.5 +3. [Apache Maven](http://maven.apache.org/) >=3.3 +4. Familiarity with the [Spring Framework](http://www.springsource.org/) +5. Internet connectivity + +Depending on choice of configuration components, there may be additional requirements such as LDAP directory, +database, and caching infrastructure. In most cases, however, requirements should be self evident to deployers who +choose components with clear hardware and software dependencies. In any case where additional requirements are +not obvious, the discussion of component configuration should mention system, software, hardware, and other +requirements. + + +## Servlet Containers +There is no officially supported servlet container for CAS, but [Apache Tomcat](http://tomcat.apache.org/) is the most +commonly used. Support for a particular servlet container depends on the expertise of community members, but the +following are known to work well and should receive first-class support on the +[Community Discussion Mailing List](../Mailing-Lists.html): + +* [JBoss](http://www.jboss.org/) +* [Jetty](http://www.eclipse.org/jetty/) +* [GlassFish](http://glassfish.java.net/) +* [WebSphere](http://www.ibm.com/software/websphere/) + + +## Apache Maven +CAS uses Maven for building and creating a deployable package for instllation into a Java servlet container. Maven is +also strongly recommended for configuration management required for the CAS installation process. CAS is fundamentally +a complex software product that becomes embedded and tighly integrated into the software environment of an institution. +For this reason it tends to require customization well beyond turnkey solutions, and the integration requirements tend +to change over time. A source-based installation process like +[Maven WAR overlay](../installation/Maven-Overlay-Installation.html) provides a straightforward and flexible solution +to complex and dynamic requirements. While it admittedly requires a high up-front cost in learning, it reaps numerous +benefits in the long run + + +## Spring Framework +CAS uses the many aspects of the Spring Framework; most notably, +[Spring MVC](http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/mvc.html) and +[Spring Webflow](http://www.springsource.org/spring-web-flow). Spring provides a complete and extensible framework for +the core CAS codebase as well as for deployers; it's straightforward to customize or extend CAS behavior by hooking +CAS and Spring API extension points. General knowledge of Spring is beneficial to understanding the interplay among +some framework compoents, but it's not strictly required. The XML-based configuration used to configure CAS and Spring +components, however, is a core concern for installation, customization, and extension. Competence with XML generally +and the +[Spring IOC Container](http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/beans.html) +in particular are prerequisites to CAS installation. + + +## Internet Connectivity +Internet connectivity is generally required for the build phase of any Maven-based project, including the recommended +Maven WAR overlays used to install CAS. Maven resolves dependencies by searching online repositories containing +artifacts (jar files in most cases) that are downloaded and installed locally. While it is possible to override this +behavior by alterning Maven configuration settings, it is considered advanced usage and not supported. + +A common solution to overcoming lack of Internet connectivity on a CAS server is to build CAS on a dedicated build +host with internet connectivity. The `cas.war` file produced by the build is subsequently copied to the CAS server +for deployment. diff --git a/cas-server-documentation/planning/Security-Guide.md b/cas-server-documentation/planning/Security-Guide.md new file mode 100644 index 000000000000..82b2b1c5034b --- /dev/null +++ b/cas-server-documentation/planning/Security-Guide.md @@ -0,0 +1,224 @@ +--- +layout: default +title: CAS - Security Guide +--- + +# Security Guide +CAS is security software that provides secure Web-based single sign-on to Web-based applications. Single sign-on +provides a win/win in terms of security and convenience: it reduces password exposure to a single, trusted credential +broker while transparently providing access to multiple services without repetitious logins. The use of CAS generally +improves the security environment, but there are several CAS configuration, policy, and deployment concerns that should +be considered to achieve suitable security. + + +## System Security Considerations + + +### Secure Transport (https) +All communication with the CAS server MUST occur over a secure channel (i.e. TLSv1). There are two primary +justifications for this requirement: + +1. The authentication process requires transmission of security credentials. +2. The CAS ticket-granting ticket is a bearer token. + +Since the disclosure of either data would allow impersonation attacks, it's vitally important to secure the +communication channel between CAS clients and the CAS server. + +Practically, it means that all CAS urls must use HTTPS, but it **also** means that all connections from the CAS server to the application must be done using HTTPS: + +- when the generated service ticket is sent back to the application on the "service" url +- when a proxy callback url is called. + + +### Connections to Dependent Systems +CAS commonly requires connections to other systems such as LDAP directories, databases, and caching services. +We generally recommend to use secure transport (SSL/TLS, IPSec) to those systems where possible, but there may +be compensating controls that make secure transport uncessary. Private networks and corporate networks with strict +acces controls are common exceptions, but secure transport is recommended nonetheless. +Client certification validation can be another good solution for LDAP to bring sufficient security. + +As stated previously, connections to other systems must be secured. But if the CAS server is deployed on several nodes, the same applies to the CAS server itself. If a cache-based ticket registry runs without any security issue on a single CAS server, synchronization can become a security problem when using multiple nodes if the network is not protected. + +Any disk storage is also vulnerable if not properly secured. EhCache overflow to disk may be turned off to increase protection whereas advanced encryption data mechanism should be used for the database disk storage. + +## Deployment-Driven Security Features +CAS supports a number of features that can be leveraged to implement various security policies. +The following features are provided through CAS configuration and CAS client integration. Note that many features +are available out of the box, while others require explicit setup. + + +### Forced Authentication +Many CAS clients and supported protocols support the concept of forced authentication whereby a user must +reauthenticate to access a particular service. The CAS protocols support forced authentication via the _renew_ +parameter. Forced authentication provides additional assurance in the identity of +the principal of an SSO session since the user must verify his or her credentials prior to access. +Forced authentication is suitable for services where higher security is desired or mandated. Typically forced +authentication is configured on a per-service basis, but the [service management](#service-management) facility +provides some support for implementing forced authentication as a matter of centralized security policy. +Forced authentication may be combined with [multi-factor authentication](#multifactor-authentication) features to +implement arbitrary service-specific access control policy. + + +### Passive Authentication +Some CAS protocols support passive authentication where access to a CAS-protected service is granted anonymously +when requested. The CASv2 and CASv3 protocols support this capability via the _gateway_ feature. Passive authentication +complements forced authentication; where forced authentication requires authentication to access a service, passive +authentication permits service access, albeit anonymously, without authentication. + + +### Proxy Authentication +Proxy authentication, or delegated authentication, provides a powerful, important, and potentially security-improving +feature of CAS. Proxy authentication is supported by the CASv2 and CASv3 protocols and is mediated by proxy tickets +that are requested by a service on behalf of a user; thus the service proxies authentication for the user. +Proxy authentication is commonly used in cases where a service cannot interact directly with the user and as an +alternative to replaying end-user credentials to a service. + +However, proxy tickets carry risk in that services accepting proxy tickets are responsible for validating the +proxy chain (the list of services through which the end-user's authentication have been delegated to arrive at +the ticket validating service). Services can opt out of accepting proxy tickets entirely (and avoid +responsibility for validating proxy chains) by simply validating tickets against the /serviceValidate +validation endpoint, but experience has shown it's easy to be confused about this and configure to +unintentionally use the /proxyValidate endpoint yet not scrutinize any proxy chains that appear in the +ticket validation response. Thus proxy authentication requires careful configuration for proper security controls; +it is recommended to disable proxy authentication components at the CAS server if proxy authentication is not +needed. + +Historically any service could obtain a proxy-granting ticket and from it a proxy ticket to access any other service. +In other words, the security model is decentralized rather than centralized. The service management facility affords +some centralized control of proxy authentication by exposing a proxy authentication flag that can enabled or disabled +on a per-service basis. By default registered services are not granted proxy authentication capability. + + +### Multi-factor Authentication +CAS provides support for multi-factor authentication in one of two modes: global and per-service. The global case +where multiple credentials are invariably required on the login form is straightforward: the user interface is +modified to accept multiple credentials and authentication components are configured to require successful +authentication of all provided credentials. + +The per-service case is both more interesting and more complicated: + +* Levels of identity assurance (LOA) for credentials and groups of credentials must be established. +* Security policy versus credential LOA must be established per service. +* Service access policy must be configured via the [service management](#service-management) facility. + +The first two tasks are vital but outside the scope of this document. Application of service access policy via the +service management facility is implemented by declaring the +[authentication handlers](../installation/Configuring-Authentication-Components.html#authentication-handlers) +that must successfully authenticate credentials in order to permit access; for example, an LDAP authentication +handler and an RSA SecureID authentication handler. + +Since multi-factor authentication requires development of institutional security policy, advanced component +configuration (and possibly custom component development), and UI design, it should be regarded more as a framework +than a feature. See the +[multi-factor configuration](../installation/Configuring-Authentication-Components.html#multifactor-authentication-mfa) +section for detailed discussion of configuration concerns and implementation recommendations. + + +### Credential Caching and Replay +The _ClearPass_ extension provides a mechanism to capture primary authentication credentials, cache them (encrypted), +and replay on demand as needed to access legacy services. While [proxy authentication](#proxy-authentication) +is recommended in lieu of password replay, it may be required to integrate legacy services with CAS. See the +[ClearPass](../integration/ClearPass.html) documentation for detailed information. + + +### Service Management +The service management facility provides a number of service-specific configuration controls that affect security +policy and provide some support for centralized security policy. (Note that CAS has historically supported the +decentralized security policy model.) Some highlights of service management controls: + +* Authorized services +* Forced authentication +* Attribute release +* Proxy authentication control +* Theme control +* Multi-factor service access policy + +The service management facility is comprised of a service registry containing one or more registered services, each +of which specifies the management controls above. The service registry can be controlled via static configuration files, +a Web user interface, or both. See the [Service Management](../installation/Service-Management.html) section for more +information. + +
Authorized Services

+As a security best practice, it is strongly recommended to limit the service management facility +to only include the list of known applications that are authorized to use CAS. Leaving the management interface +open for all applications may create an opportunity for security attacks. +

+ +### Ticket Expiration Policies +Ticket expiration policies are a primary mechanism for implementing security policy. Ticket expiration policy allows +control of some important aspects of CAS SSO session behavior: + +* SSO session duration (sliding expiration, absolute) +* Ticket reuse + +See the [Configuring Ticketing Components](../installation/Configuring-Ticketing-Components.html) section for a +detailed discussion of the various expiration policies and configuration instructions. + + +### Single Sign-Out +Single sign-out, or single log-out (SLO), is a feature by which CAS services are notified of the termination of a CAS +SSO session with the expectation that services terminate access for the SSO session owner. While single sign-out can +improve security, it is fundamentally a best-effort facility and may not actually terminate access to all services +consumed during an SSO session. The following compensating controls may be used to improve risks associated with +single sign-out shortcomings: + +* Require forced authentication for sensitive services +* Reduce application session timeouts +* Reduce SSO session duration + +SLO can happen in two ways: from the CAS server (back-channel logout) and/or from the browser (front-channel logout). +For back-channel logout, the SLO process relies on the `SimpleHttpClient` class which has a threads pool: its size must be defined to properly treat all the logout requests. +Additional not-already-processed logout requests are temporarily stored in a queue before being sent: its size is defined to 20% of the global capacity of the threads pool and can be adjusted. +Both sizes are critical settings of the CAS system and their values should never exceed the real capacity of the CAS server. + + +### Login Throttling +CAS supports a policy-driven feature to limit successive failed authentication attempts to help prevent brute force +and denial of service attacks. The feature is beneficial in environments where back-end authentication stores lack +equivalent features. In cases where this support is available in underlying systems, we encourage using it instead +of CAS features; the justification is that enabling support in underlying systems provides the feature in all dependent +systems including CAS. See the +[login throttling configuration](../installation/Configuring-Authentication-Components.html#login-throttling) +section for further information. + + +###Credential Encryption +An open source product called [Java Simplified Encryption](http://www.jasypt.org/cli.html) allows you to replace clear text passwords in files with encrypted strings that are decrypted at run time. Jasypt can be integrated into the Spring configuration framework so that property values are decrypted as the configuration file is loaded. Jasypt's approach replaces the the property management technique with one that recognizes encrypted strings and decrypts them. This method uses password-based encryption, which means that the system still needs a secret password in order to decrypt our credentials. We don't want to simply move the secret from one file to another, and Jasypt avoids that by passing the key as an environment variable or even directly to the application through a web interface each time it is deployed. + +This ability is beneficial since it removes the need to embed plain-text credentials in configuration files, and allows the adopter to securely keep track of all encrypted settings in source control systems, safely sharing the build configuration with others. Sensitive pieces of data are only restricted to the deployment environment. + +### CAS Security Filter +The CAS project provides a number of a blunt [generic security filters][cas-sec-filter] suitable for patching-in-place Java CAS server and Java CAS client deployments vulnerable to certain request parameter based bad-CAS-protocol-input attacks. +The filters are configured to sanitize authentication request parameters and reject the request if it is not compliant with the CAS protocol in the event that for instance, a parameter is repeated multiple times, includes multiple values, contains unacceptable values, etc. + +It is **STRONGLY** recommended that all CAS deployments be evaluated and include this configuration if necessary to prevent protocol attacks in situations where the CAS container and environment are unable to block malicious and badly-configured requests. + +## User-Driven Security Features +The following features may be employed to afford some user control of the SSO experience. + + +### Long Term Authentication +The long term authentication feature, commonly referred to as "Remember Me", is selected (usually via checkbox) on the CAS login form to avoid reauthentication for an extended period of time. Long term authentication allows users to elect additional convenience at the expense of reduced security. The extent of reduced security is a function of the characteristics of the device used to establish a CAS SSO session. A long-term SSO session established from a device owned or operated by a single user is marginally less secure than a standard CAS SSO session. The only real concern would be the increased lifetime and resulting increased exposure of the CAS ticket-granting ticket. Establishing a long-term CAS SSO session from a shared device, on the other hand, may dramatically reduce security. +The likelihood of artifacts from previous SSO sessions affecting subsequent SSO sessions established by other users, even in the face of single sign-out, may increase the likelihood of impersonation. While there are no feasible mitigations for improving security of long-term SSO sessions on a shared device, educating users on the inherent risks may improve overall security. + +It is important to note that forced authentication supercedes long term authentication, thus if a service were +configured for forced authentication, authentication would be required for service access even in the context of a +long-term session. + +Long term authentication support must be explicitly enabled through +[configuration and UI customization](../installation/Configuring-Authentication-Components.html#long-term-authentication) +during the installation process. Thus deployers choose to offer long-term authentication support, and when available +users may elect to use it via selection on the CAS login form. + + +### Warn +CAS supports optional notification of service access during an established SSO session. By default CAS +transparently requests tickets needed for service access and presents them to the target service for validation, +whereby upon successful validation access to the service is permitted. In most cases this happens nearly instantly +and the user is not aware of the CAS authentication process required to access CAS-enabled services. There may be +some security benefit to awareness of this process, and CAS supports a _warn_ flag that may be selected by the user +on the CAS login screen to provide an interstitial notification page that is displayed prior to accessing a service. +By default the notification page offers the user an option to proceed with CAS authentication or abort by +navigating away from the target service. + +[cas-sec-filter]: https://github.com/Jasig/cas-server-security-filter diff --git a/cas-server-documentation/protocol/CAS-Protocol-Specification.md b/cas-server-documentation/protocol/CAS-Protocol-Specification.md new file mode 100644 index 000000000000..cf6f70fdd2d9 --- /dev/null +++ b/cas-server-documentation/protocol/CAS-Protocol-Specification.md @@ -0,0 +1,1717 @@ +--- +layout: default +title: CAS - CAS Protocol Specification +--- + + + +*CAS Protocol 3.0 Specification* +================================ + +**Authors, Version** +==================== + +Author: Drew Mazurek + +Contributors: + +- Susan Bramhall +- Howard Gilbert +- Andy Newman +- Andrew Petro +- Robert Oschwald [CAS 3.0] +- Misagh Moayyed + +Version: 3.0.1 + +Release Date: 2015-01-13 + +Copyright © 2005, Yale University + +Copyright © 2015, Apereo, Inc. + + + +**1. Introduction** +=================== + +This is the official specification of the CAS 1.0, 2.0 and 3.0 protocols. + +The Central Authentication Service (CAS) is a single-sign-on / single-sign-off protocol +for the web. +It permits a user to access multiple applications while providing their +credentials (such as userid and password) only once to a central CAS Server +application. + + + + +**1.1. Conventions & Definitions** +---------------------------------- + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be +interpreted as described in RFC 2119[1](<#1>). + +- "Client" refers to the end user and/or the web browser. + +- "CAS Client" refers to the software component that is integrated with a web + application and interacts with the CAS server via CAS protocol. + +- "Server" refers to the Central Authentication Service server. + +- "Service" refers to the application the client is trying to access. + +- "Back-end service" refers to the application a service is trying to access + on behalf of a client. This can also be referred to as the "target service." + +- "SSO" refers to Single Sign on. + +- "SLO" refers to Single Logout. + +- "\" is a bare line feed (ASCII value 0x0a). + + + +**1.2 Reference Implementation** +-------------------------------- +The Apereo CAS-Server [8](<#8>) is the official reference implementation of the +CAS Protocol Specification. + +Apereo CAS Server 4.x supports the CAS Protocol 3.0 Specification. + + + + +**2. CAS URIs** +=============== + +CAS is an HTTP[2](<#2>),[3](<#3>)-based protocol that requires each of its +components to be accessible through specific URIs. This section will discuss +each of the URIs: + +| URI | Description +|-----------------------------------|------------------------------------------- +| `/login` | credential requestor / acceptor +| `/logout` | destroy CAS session (logout) +| `/validate` | service ticket validation +| `/serviceValidate` | service ticket validation [CAS 2.0] +| `/proxyValidate` | service/proxy ticket validation [CAS 2.0] +| `/proxy` | proxy ticket service [CAS 2.0] +| `/p3/serviceValidate` | service ticket validation [CAS 3.0] +| `/p3/proxyValidate` | service/proxy ticket validation [CAS 3.0] + + + +**2.1. /login as credential requestor** +--------------------------------------- + +The `/login` URI operates with two behaviors: as a credential requestor, and as +a credential acceptor. It responds to credentials by acting as a credential +acceptor and otherwise acts as a credential requestor. + +If the client has already established a single sign-on session with CAS, the +web browser presents to CAS a secure cookie containing a string identifying +a ticket-granting ticket. This cookie is called the ticket-granting cookie. +If the ticket-granting cookie keys to a valid ticket-granting ticket, CAS MAY +issue a service ticket provided all the other conditions in this specification +are met. +See Section [3.6](<#head3.6>) for more information on ticket-granting cookies. + + + +### **2.1.1. parameters** + +The following HTTP request parameters may be passed to `/login` while it is +acting as a credential requestor. They are all case-sensitive, and they all +MUST be handled by `/login`. + +- `service` [OPTIONAL] - the identifier of the application the client is + trying to access. In almost all cases, this will be the URL of the + application. As a HTTP request parameter, this URL value MUST be + URL-encoded as described in section 2.2 of RFC 3986 [[4](<#4>)]. + If a `service` is not specified and a single sign-on session does not yet + exist, CAS SHOULD request credentials from the user to initiate a single + sign-on session. If a `service` is not specified and a single sign-on + session already exists, CAS SHOULD display a message notifying the client + that it is already logged in. + + > Note: It is STRONGLY RECOMMENDED that all `service` urls be filtered via + > the service management tool, such that only authorized and known + > client applications would be able to use the CAS server. + > Leaving the service management tool open to allow lenient access to + > all applications will potentially increase the risk of service attacks + > and other security vulnerabilities. Furthermore, it is RECOMMENDED that + > only secure protocols such as `https` be allowed for client applications + > for further strengthen the authenticating client. + +- `renew` [OPTIONAL] - if this parameter is set, single sign-on will be + bypassed. In this case, CAS will require the client to present credentials + regardless of the existence of a single sign-on session with CAS. This + parameter is not compatible with the `gateway` parameter. Services + redirecting to the `/login` URI and login form views posting to the `/login` + URI SHOULD NOT set both the `renew` and `gateway` request parameters. + Behavior is undefined if both are set. It is RECOMMENDED that CAS + implementations ignore the `gateway` parameter if `renew` is set. + It is RECOMMENDED that when the `renew` parameter is set its value be "true". + +- `gateway` [OPTIONAL] - if this parameter is set, CAS will not ask the client + for credentials. If the client has a pre-existing single sign-on session + with CAS, or if a single sign-on session can be established through + non-interactive means (i.e. trust authentication), CAS MAY redirect the + client to the URL specified by the `service` parameter, appending a valid + service ticket. (CAS also MAY interpose an advisory page informing the + client that a CAS authentication has taken place.) If the client does not + have a single sign-on session with CAS, and a non-interactive authentication + cannot be established, CAS MUST redirect the client to the URL specified by + the `service` parameter with no "ticket" parameter appended to the URL. If + the `service` parameter is not specified and `gateway` is set, the behavior + of CAS is undefined. It is RECOMMENDED that in this case, CAS request + credentials as if neither parameter was specified. This parameter is not + compatible with the `renew` parameter. Behavior is undefined if both are + set. It is RECOMMENDED that when the `gateway` parameter is set its value be + "true". + +- `method` [OPTIONAL, CAS 3.0] - The `method` to be used when sending responses. + While native HTTP redirects (GET) may be utilized as the default method, + applications that require a POST response can use this parameter to indicate + the method type. It is up to the CAS server implementation to determine + whether or not POST responses are supported. + + + +### **2.1.2. URL examples of /login** + +Simple login example: + +`https://cas.example.org/cas/login?service=http%3A%2F%2Fwww.example.org%2Fservice` + +Don't prompt for username/password: + +`https://cas.example.org/cas/login?service=http%3A%2F%2Fwww.example.org%2Fservice&gateway=true` + +Always prompt for username/password: + +`https://cas.example.org/cas/login?service=http%3A%2F%2Fwww.example.org%2Fservice&renew=true` + +Use POST responses instead of redirects: + +`https://cas.example.org/cas/login?method=POST&service=http%3A%2F%2Fwww.example.org%2Fservice` + + + +### **2.1.3. response for username/password authentication** + +When `/login` behaves as a credential requestor, the response will vary depending +on the type of credentials it is requesting. In most cases, CAS will respond by +displaying a login screen requesting a username and password. This page MUST +include a form with the parameters, "username", "password", and "lt". The form +MAY also include the parameter "warn". If `service` was specified to `/login`, +`service` MUST also be a parameter of the form, containing the value originally +passed to `/login`. These parameters are discussed in detail in Section [2.2.1](#head2.2.1). The +form MUST be submitted through the HTTP POST method to `/login` which will then +act as a credential acceptor, discussed in Section [2.2](#head2.2). + + + +### **2.1.4. response for trust authentication** + +Trust authentication accommodates consideration of arbitrary aspects of the +request as a basis for authentication. The appropriate user experience for trust +authentication will be highly deployer-specific in consideration of local policy +and of the logistics of the particular authentication mechanism implemented. + +When `/login` behaves as a credential requestor for trust authentication, its +behavior will be determined by the type of credentials it will be receiving. If +the credentials are valid, CAS MAY transparently redirect the user to the +service. Alternately, CAS MAY display a warning that credentials were presented +and allow the client to confirm that it wants to use those credentials. It is +RECOMMENDED that CAS implementations allow the deployer to choose the preferred +behavior. If the credentials are invalid or non-existent, it is RECOMMENDED that +CAS displays to the client the reason why authentication failed, and possibly present +the user with alternate means of authentication (e.g. username/password +authentication). + + + +### **2.1.5. response for single sign-on authentication** + +If the client has already established a single sign-on session with CAS, the +client will have presented its HTTP session cookie to `/login` and behavior will +be handled as in Section [2.2.4](#head2.2.4). However, if the `renew` parameter +is set, the behavior will be handled as in Section [2.1.3](#head2.1.3) or +[2.1.4](#head2.1.4). + + + +**2.2. /login as credential acceptor** +-------------------------------------- + +When a set of accepted credentials are passed to `/login`, `/login` acts as a +credential acceptor and its behavior is defined in this Section. + + + +### **2.2.1. parameters common to all types of authentication** + +The following HTTP request parameters MAY be passed to `/login` while it is acting +as a credential acceptor. They are all case-sensitive and they all MUST be +handled by `/login`. + +- `service` [OPTIONAL] - the URL of the application the client is trying to + access. As a HTTP request parameter, this URL value MUST be URL-encoded as + described in Section 2.2 of RFC 1738 [[4](<#4>)]. CAS MUST redirect the + client to this URL upon successful authentication. This is discussed in + detail in Section [2.2.4](#head2.2.4). If the CAS Server operates in + non-open mode (Service URLs allowed to use the CAS Server are registered + within the CAS Server), the CAS Server MUST deny operation and print out + a meaningful message if a non-authorized service URL is presented. + + > Note: It is STRONGLY RECOMMENDED that all `service` urls be filtered via + > the service management tool, such that only authorized and known + > client applications would be able to use the CAS server. + > Leaving the service management tool open to allow lenient access to + > all applications will potentially increase the risk of service attacks + > and other security vulnerabilities. Furthermore, it is RECOMMENDED that + > only secure protocols such as `https` be allowed for client applications + > for further strengthen the authenticating client. + +- `warn` [OPTIONAL] - if this parameter is set, single sign-on MUST NOT be + transparent. The client MUST be prompted before being authenticated to + another service. + +- `method` [OPTIONAL] - The `method` to be used when sending responses. See + section [2.1.1](<#head2.1.1>) for details + + + +### **2.2.2. parameters for username/password authentication** + +In addition to the OPTIONAL parameters specified in Section [2.2.1](#head2.2.1), +the following HTTP request parameters MUST be passed to `/login` while it is +acting as a credential acceptor for username/password authentication. They are +all case-sensitive. + +- `username` [REQUIRED] - the username of the client that is trying to log in + +- `password` [REQUIRED] - the password of the client that is trying to log in + +- `lt` [REQUIRED] - a login ticket. This is provided as part of the login form + discussed in Section [2.1.3](#head2.1.3). The login ticket itself is discussed + in Section [3.5](#head3.5). + +- `rememberMe` [OPTIONAL, CAS 3.0] - if this parameter is set, a Long-Term + Ticket Granting Ticket might be created by the CAS server (refered to as + Remember-Me support). It is subject to the CAS server configuration whether + Long-Term Ticket Granting Tickets are supported or not. + +> Note: When Long-Term Ticket Granting Tickets (Remember Me) are supported by +> the CAS Server, security considerations MUST be taken into account. This for +> example includes shared computer usage. On CAS client systems it might be +> necessary to handle Remember-Me logins different. See Section +> [4.1](<#head4.1>) for details. + + + +### **2.2.3. parameters for trust authentication** + +There are no REQUIRED HTTP request parameters for trust authentication. Trust +authentication MAY be based on any aspect of the HTTP request. + + + +### **2.2.4. response** + +One of the following responses MUST be provided by `/login` when it is operating +as a credential acceptor. + +- successful login: redirect the client to the URL specified by the `service` + parameter in a manner that will not cause the client's credentials to be + forwarded to the `service`. This redirection MUST result in the client issuing + a GET request to the `service`. The request MUST include a valid service + ticket, passed as the HTTP request parameter, "ticket". See [Appendix + B](<#head_appdx_b>) for more information. If `service` was not specified, + CAS MUST display a message notifying the client that it has successfully + initiated a single sign-on session. + +- failed login: return to `/login` as a credential requestor. It is RECOMMENDED + in this case that the CAS server display an error message to the user + describing why login failed (e.g. bad password, locked account, etc.), + and if appropriate, provide an opportunity for the user to attempt to + login again. + + + +**2.3. /logout** +---------------- + +`/logout` destroys a client's single sign-on CAS session. The ticket-granting +cookie (Section [3.6](#head3.6)) is destroyed, and subsequent requests to `/login` will not +obtain service tickets until the user again presents primary credentials (and +thereby establishes a new single sign-on session). + + + +### **2.3.1. parameters** + +The following HTTP request parameter MAY be specified to `/logout`. It is case +sensitive and SHOULD be handled by `/logout`. + +- `service` [OPTIONAL, CAS 3.0] - if a `service` parameter is specified, the + browser might be automatically redirected to the URL specified by `service` + after the logout was performed by the CAS server. If redirection by the + CAS Server is actually performed depends on the server configuration. + As a HTTP request parameter, the `service` value MUST be URL-encoded as + described in Section 2.2 of RFC 1738 [[4](<#4>)]. + + > Note: It is STRONGLY RECOMMENDED that all `service` urls be filtered via + > the service management tool, such that only authorized and known + > client applications would be able to use the CAS server. + > Leaving the service management tool open to allow lenient access to + > all applications will potentially increase the risk of service attacks + > and other security vulnerabilities. Furthermore, it is RECOMMENDED that + > only secure protocols such as `https` be allowed for client applications + > for further strengthen the authenticating client. + + > Note: The `url` parameter defined in the former CAS 2.0 specification is + > not a valid parameter in CAS 3.0 anymore. CAS Servers MUST ignore given + > `url` parameters. + > A CAS client MAY provide the `service` parameter as described above, + > as this ensures the parameter is validated against the registered service + > URLs when operating in non-open mode. See [2.3.2](#head2.3.2) for details. + + + +### **2.3.2. response** + +[CAS 1.0, CAS 2.0] `/logout` MUST display a page stating that the user has been +logged out. If the "url" request parameter is implemented, `/logout` SHOULD also +provide a link to the provided URL as described in Section [2.3.1](#head2.3.1). + +[CAS 3.0] `/logout` MUST display a page stating that the user has been logged out +if no `service` parameter was provided. If a `service` request parameter with an +encoded URL value is provided, the CAS server redirects to the given service URL +after successful logout. + +> Note: When CAS Server operates in non-open mode (allowed Service URLs are +> registered within the CAS Server), the CAS server MUST ensure that only +> registered [service] parameter Service URLs are accepted for redirection. +> The `url` parameter defined in the former CAS 2.0 specification is +> not a valid parameter in CAS 3.0 anymore. CAS Servers MUST ignore given +> `url` parameters. + + + +### **2.3.3 Single Logout** + +The CAS Server MAY support Single Logout (SLO). SLO means that the user gets +logged out not only from the CAS Server, but also from all visited CAS client +applications. If SLO is supported by the CAS Server, the CAS Server MUST send a +HTTP POST request containing a logout XML document (see [Appendix +C](<#head_appdx_c>)) to all service URLs provided to CAS during this CAS session +whenever a Ticket Granting Ticket is explicitly expired by the user (e.g. during logout). +CAS Clients that do not support the SLO POST requests MUST ignore these requests. +SLO requests MAY also be initiated by the CAS Server upon TGT idle timeout. + + + + + +#### **2.3.3.1 Server behaviour** + +The CAS Server SHALL ignore all errors that might occur on a Single Logout POST +request to CAS Client applications service URLs. This ensures that any errors +while sending the POST request do not disturb CAS Server performance and +availability ("fire and forget"). + + + + + +#### **2.3.3.2 Client behaviour** + +Handling the logout POST request data is up to the CAS client. It is RECOMMENDED +to logout the user from the application identified by the service ticket id sent +in the SLO POST request. +If the client supports SLO POST request handling, the client SHALL return a HTTP success +status code. + + + + + +## **2.4. /validate [CAS 1.0]** + +`/validate` checks the validity of a service ticket. `/validate` is part of the CAS +1.0 protocol and thus does not handle proxy authentication. CAS MUST respond +with a ticket validation failure response when a proxy ticket is passed to +`/validate`. + + + + + +### **2.4.1. parameters** + +The following HTTP request parameters MAY be specified to `/validate`. They are +case sensitive and MUST all be handled by `/validate`. + +- `service` [REQUIRED] - the identifier of the service for which the ticket was + issued, as discussed in Section 2.2.1. + As a HTTP request parameter, the `service` value MUST be URL-encoded as + described in Section 2.2 of RFC 1738 [[4](<#4>)]. + + > Note: It is STRONGLY RECOMMENDED that all `service` urls be filtered via + > the service management tool, such that only authorized and known + > client applications would be able to use the CAS server. + > Leaving the service management tool open to allow lenient access to + > all applications will potentially increase the risk of service attacks + > and other security vulnerabilities. Furthermore, it is RECOMMENDED that + > only secure protocols such as `https` be allowed for client applications + > for further strengthen the authenticating client. + +- `ticket` [REQUIRED] - the service ticket issued by `/login`. Service tickets are + described in Section 3.1. + +- `renew` [OPTIONAL] - if this parameter is set, ticket validation will only + succeed if the service ticket was issued from the presentation of the user's + primary credentials. It will fail if the ticket was issued from a single + sign-on session. + + + + + +### **2.4.2. response** + +`/validate` will return one of the following two responses: + +On ticket validation success: + +yes\ + +On ticket validation failure: + +no\ + + + + + +### **2.4.3. URL examples of /validate** + +Simple validation attempt: + +`https://cas.example.org/cas/validate?service=http%3A%2F%2Fwww.example.org%2Fservice&ticket=ST-1856339-aA5Yuvrxzpv8Tau1cYQ7` + +Ensure service ticket was issued by presentation of primary credentials: + +`https://cas.example.org/cas/validate?service=http%3A%2F%2Fwww.example.org%2Fservice&ticket=ST-1856339-aA5Yuvrxzpv8Tau1cYQ7&renew=true` + + + + + +**2.5. /serviceValidate [CAS 2.0]** +----------------------------------- + +`/serviceValidate` checks the validity of a service ticket and returns an XML-fragment response. +`/serviceValidate` MUST also generate and issue proxy-granting tickets when requested. +`/serviceValidate` MUST NOT return a successful authentication if it receives a proxy ticket. +It is RECOMMENDED that if `/serviceValidate` receives a proxy ticket, the error message in the XML +response SHOULD explain that validation failed because a proxy ticket was passed +to `/serviceValidate`. + + + + + +### **2.5.1. parameters** + +The following HTTP request parameters MAY be specified to `/serviceValidate`. They +are case sensitive and MUST all be handled by `/serviceValidate`. + +- `service` [REQUIRED] - the identifier of the service for which the ticket was + issued, as discussed in Section [2.2.1](#head2.2.1). + As a HTTP request parameter, the `service` value MUST be URL-encoded as + described in Section 2.2 of RFC 1738 [[4](<#4>)]. + + > Note: It is STRONGLY RECOMMENDED that all `service` urls be filtered via + > the service management tool, such that only authorized and known + > client applications would be able to use the CAS server. + > Leaving the service management tool open to allow lenient access to + > all applications will potentially increase the risk of service attacks + > and other security vulnerabilities. Furthermore, it is RECOMMENDED that + > only secure protocols such as `https` be allowed for client applications + > for further strengthen the authenticating client. + +- `ticket` [REQUIRED] - the service ticket issued by `/login`. Service tickets are + described in Section [3.1](#head3.1). + +- `pgtUrl` [OPTIONAL] - the URL of the proxy callback. Discussed in Section + [2.5.4](#head2.5.4). + As a HTTP request parameter, the "pgtUrl" value MUST be URL-encoded as + described in Section 2.2 of RFC 1738 [[4](<#4>)]. + +- `renew` [OPTIONAL] - if this parameter is set, ticket validation will only + succeed if the service ticket was issued from the presentation of the user's + primary credentials. It will fail if the ticket was issued from a single + sign-on session. + + + + + +### **2.5.2. response** + +`/serviceValidate` will return an XML-formatted CAS serviceResponse as described +in the XML schema in Appendix A. Below are example responses: + +**On ticket validation success:** + +{% highlight xml %} + + + username + PGTIOU-84678-8a9d... + + +{% endhighlight %} +**On ticket validation failure:** + +{% highlight xml %} + + + Ticket ST-1856339-aA5Yuvrxzpv8Tau1cYQ7 not recognized` + + +{% endhighlight %} + +For proxy responses, see section [2.6.2](<#head2.6.2>). + + + +### **2.5.3. error codes** + +The following values MAY be used as the "code" attribute of authentication +failure responses. The following is the minimum set of error codes that all CAS +servers MUST implement. Implementations MAY include others. + +- `INVALID_REQUEST` - not all of the required request parameters were present + +- `INVALID_TICKET_SPEC` - failure to meet the requirements of validation specification + +- `UNAUTHORIZED_SERVICE_PROXY` - the service is not authorized to perform proxy authentication + +- `INVALID_PROXY_CALLBACK` - The proxy callback specified is invalid. The credentials specified + for proxy authentication do not meet the security requirements + +- `INVALID_TICKET` - the ticket provided was not valid, or the ticket did not + come from an initial login and `renew` was set on validation. The body of + the `\` block of the XML response SHOULD describe + the exact details. + +- `INVALID_SERVICE` - the ticket provided was valid, but the service specified + did not match the service associated with the ticket. CAS MUST invalidate + the ticket and disallow future validation of that same ticket. + +- `INTERNAL_ERROR` - an internal error occurred during ticket validation + +For all error codes, it is RECOMMENDED that CAS provide a more detailed message +as the body of the `\` block of the XML response. + + + + + +### **2.5.4. proxy callback** + +If a service wishes to proxy a client's authentication to a back-end service, it +must acquire a proxy-granting ticket (PGT). Acquisition of this ticket is handled +through a proxy callback URL. This URL will uniquely and securely identify the +service that is proxying the client's authentication. The back-end service can +then decide whether or not to accept the credentials based on the proxying +service identifying callback URL. + +The proxy callback mechanism works as follows: + +1. The service that is requesting a proxy-granting ticket (PGT) specifies upon + initial service ticket or proxy ticket validation the HTTP request parameter + "pgtUrl" to `/serviceValidate` (or `/proxyValidate`). This is a callback URL of + the service to which CAS will connect to verify the service's identity. This + URL MUST be HTTPS and CAS MUST evaluate the endpoint to establish peer trust. + Building trust at a minimum involves utilizing PKIX and employing container trust to + validate the signature, chain and the expiration window of the certificate of the + callback url. The generation of the proxy-granting-ticket or the corresponding + proxy granting ticket IOU may fail due to the proxy callback url failing to meet the minimum + security requirements such as failure to establishing trust between peers or unresponsiveness + of the endpoint, etc. In case of failure, no proxy-granting ticket will be issued and the CAS + service response as described in Section [2.5.2](#head2.5.2) MUST NOT contain a + `\` block. At this point, the issuance of a + proxy-granting ticket is halted and service ticket validation will + fail. Otherwise, the process will proceed normally to step 2. + +2. CAS uses an HTTP GET request to pass the HTTP request parameters `pgtId` and + `pgtIou` to the pgtUrl endpoint. These entities are discussed in Sections [3.3](#head3.3) and + [3.4](#head3.4), respectively. If the proxy callback url specifies any parameters, those + MUST be preserved. CAS MUST also ensure that the endpoint is reachable by verifying + the response HTTP status code from the GET request, as detailed in step #3. If the + proxy service fails to authenticate or the endpoint responds with an unacceptable status + code, proxy authentication MUST fail and CAS MUST respond with the appropriate error code + as is described in section [2.5.3](<#head2.5.3>). + +3. If the HTTP GET returns an HTTP status code of 200 (OK), CAS MUST respond to + the `/serviceValidate` (or `/proxyValidate`) request with a service response + (Section [2.5.2](#head2.5.2)) containing the proxy-granting ticket IOU (Section [3.4](#head3.4)) + within the `\` block. If the HTTP GET returns any + other status code, except HTTP 3xx redirects, CAS MUST respond to the + `/serviceValidate` (or `/proxyValidate`) request with a service response that + MUST NOT contain a `\` block. CAS MAY follow any HTTP + redirects issued by the `pgtUrl`. However, the identifying callback URL + provided upon validation in the `\` block MUST be the same URL that was + initially passed to `/serviceValidate` (or `/proxyValidate`) as the `pgtUrl` + parameter. + +4. The service, having received a proxy-granting ticket IOU in the CAS + response, and both a proxy-granting ticket and a proxy-granting ticket IOU + from the proxy callback, will use the proxy-granting ticket IOU to correlate + the proxy-granting ticket with the validation response. The service will + then use the proxy-granting ticket for the acquisition of proxy tickets as + described in Section [2.7](#head2.7). + + + + + +### **2.5.5. attributes [CAS 3.0]** + +[CAS 3.0] The response document MAY include an optional \ +element for additional authentication and/or user attributes. See [Appendix +A](<#head_appdx_b>) for details. + + + +### **2.5.6. URL examples of /serviceValidate** + +Simple validation attempt: + +`https://cas.example.org/cas/serviceValidate?service=http%3A%2F%2Fwww.example.org%2Fservice&ticket=ST-1856339-aA5Yuvrxzpv8Tau1cYQ7` + +Ensure service ticket was issued by presentation of primary credentials: + +`https://cas.example.org/cas/serviceValidate?service=http%3A%2F%2Fwww.example.org%2Fservice&ticket=ST-1856339-aA5Yuvrxzpv8Tau1cYQ7&renew=true +` + +Pass in a callback URL for proxying: + +`https://cas.example.org/cas/serviceValidate?service=http%3A%2F%2Fwww.example.org%2Fservice&ticket=ST-1856339-aA5Yuvrxzpv8Tau1cYQ7&pgtUrl=https://www.example.org%2Fservice%2FproxyCallback` + + + + + +### **2.5.7 Example response with custom attributes** +{% highlight xml %} + + + username + + John + Doe + Mr. + jdoe@example.orgmailto:jdoe@example.org + staff + faculty + + PGTIOU-84678-8a9d... + + +{% endhighlight %} + + + + +**2.6. /proxyValidate [CAS 2.0]** +--------------------------------- + +`/proxyValidate` MUST perform the same validation tasks as `/serviceValidate` and +additionally validate proxy tickets. `/proxyValidate` MUST be capable of +validating both service tickets and proxy tickets. See Section +[2.5.4](<#head2.5.4>) for details. + + + + + +### **2.6.1. parameters** + +`/proxyValidate` has the same parameter requirements as `/serviceValidate`. See +Section [2.5.1](<#head2.5.1>). + + + + + +### **2.6.2. response** + +`/proxyValidate` will return an XML-formatted CAS serviceResponse as described in +the XML schema in Appendix A. Below are example responses: + +Response on ticket validation success: + +{% highlight xml %} + + + username + PGTIOU-84678-8a9d... + + https://proxy2/pgtUrl + https://proxy1/pgtUrl + + + +{% endhighlight %} + +> Note: when authentication has proceeded through multiple proxies, the order +> in which the proxies were traversed MUST be reflected in the \ +> block. The most recently-visited proxy MUST be the first proxy listed, and +> all the other proxies MUST be shifted down as new proxies are added. In the +> above example, the service identified by \ was +> visited first, and that service proxied authentication to the service +> identified by \. + +Response on ticket validation failure: + +{% highlight xml %} + + + ticket PT-1856376-1HMgO86Z2ZKeByc5XdYD not recognized + + +{% endhighlight %} + + + + +### **2.6.3 error codes** + +See section [2.5.3](<#head2.5.3>) + + + + + +### **2.6.4 URL examples of /proxyValidate** + +`/proxyValidate` accepts the same parameters as `/serviceValidate`. See Section +[2.5.5](<#head2.5.5>) for use examples, substituting "proxyValidate" for +"serviceValidate". + + + + + +**2.7. /proxy [CAS 2.0]** +------------------------- + +`/proxy` provides proxy tickets to services that have acquired proxy-granting +tickets and will be proxying authentication to back-end services. + + + + +### **2.7.1. parameters** + +The following HTTP request parameters MUST be specified to `/proxy`. They are both +case-sensitive. + +- `pgt` [REQUIRED] - the proxy-granting ticket acquired by the service during + service ticket or proxy ticket validation. +- `targetService` [REQUIRED] - the service identifier of the back-end service. + Note that not all back-end services are web services so this service identifier + will not always be an URL. However, the service identifier specified here + MUST match the `service` parameter specified to `/proxyValidate` upon validation + of the proxy ticket. + + + + +### **2.7.2. response** + +`/proxy` will return an XML-formatted CAS serviceResponse document as described in the XML +schema in [Appendix A](#head_appdx_a). Below are example responses: + +Response on request success: + +{% highlight xml %} + + + PT-1856392-b98xZrQN4p90ASrw96c8 + + +{% endhighlight %} + +Response on request failure: + +{% highlight xml %} + + + 'pgt' and 'targetService' parameters are both required + + +{% endhighlight %} + + + + +### **2.7.3. error codes** + +The following values MAY be used as the `code` attribute of authentication +failure responses. The following is the minimum set of error codes that all CAS +servers MUST implement. Implementations MAY include others. + +- `INVALID_REQUEST` - not all of the required request parameters were present + +- `UNAUTHORIZED_SERVICE` - service is unauthorized to perform the proxy request + +- `INTERNAL_ERROR` - an internal error occurred during ticket validation + +For all error codes, it is RECOMMENDED that CAS provide a more detailed message +as the body of the \ block of the XML response. + + + + + +### **2.7.4. URL example of /proxy** + +Simple proxy request: + +`https://server/cas/proxy?targetService=http%3A%2F%2Fwww.service.com&pgt=PGT-490649-W81Y9Sa2vTM7hda7xNTkezTbVge4CUsybAr` + + + + +### **2.7.4 Service Ticket Lifecycle implications** +The CAS Server implementation MAY update the parent Service Ticket (ST) lifetime upon proxy ticket generation. + +**2.8. /p3/serviceValidate [CAS 3.0]** +--------------------------------- + +`/p3/serviceValidate` MUST perform the same validation tasks as `/serviceValidate` and +additionally return user attributes in the CAS response. See +Section [2.5](<#head2.5>) and Section [2.5.7](<#head2.5.7>) for details. + + + +### **2.8.1. parameters** + +`/p3/serviceValidate` has the same parameter requirements as `/serviceValidate`. See +Section [2.5.1](<#head2.5.1>). + +**2.9. /p3/proxyValidate [CAS 3.0]** +--------------------------------- + +`/p3/proxyValidate` MUST perform the same validation tasks as `/p3/serviceValidate` and +additionally validate proxy tickets. See Section [2.8](<#head2.5>). + + + +### **2.9.1. parameters** + +`/p3/proxyValidate` has the same parameter requirements as `/p3/serviceValidate`. See +Section [2.8.1](<#head2.8.1>). + +**3. CAS Entities** +=================== + + + +**3.1. service ticket** +----------------------- + +A service ticket is an opaque string that is used by the client as a credential +to obtain access to a service. The service ticket is obtained from CAS upon a +client's presentation of credentials and a service identifier to `/login` as +described in Section [2.2](#head2.2). + + + +### **3.1.1. service ticket properties** + +- Service tickets are only valid for the service identifier that was specified + to `/login` when they were generated. The service identifier SHOULD NOT be + part of the service ticket. + +- Service tickets MUST only be valid for one ticket validation attempt. + Whether or not validation was successful, CAS MUST then invalidate the ticket, + causing all future validation attempts of that same ticket to fail. + +- CAS SHOULD expire unvalidated service tickets in a reasonable period of time + after they are issued. If a service presents an expired service ticket for + validation, CAS MUST respond with a validation failure response. + +- It is RECOMMENDED that the validation response include a descriptive message + explaining why validation failed. + +- It is RECOMMENDED that the duration a service ticket is valid before it expires + be no longer than five minutes. Local security and CAS usage considerations + MAY determine the optimal lifespan of unvalidated service tickets. + +- Service tickets MUST contain adequate secure random data so that a ticket is + not guessable. +- Service tickets MUST begin with the characters, `ST-`. + +- Services MUST be able to accept service tickets of up to 32 characters in length. + It is RECOMMENDED that services support service tickets of up to 256 characters in + length. + + + + + +**3.2. proxy ticket** +--------------------- + +A proxy ticket is an opaque string that a service uses as a credential to obtain +access to a back-end service on behalf of a client. Proxy tickets are obtained +from CAS upon a service's presentation of a valid proxy-granting ticket (Section +[3.3](<#head3.3>)), and a service identifier for the back-end service to which +it is connecting. + + + + + +### **3.2.1. proxy ticket properties** + +- Proxy tickets are only valid for the service identifier specified to `/proxy` + when they were generated. The service identifier SHOULD NOT be part of the + proxy ticket. + +- Proxy tickets MUST only be valid for one ticket validation attempt. + Whether or not validation was successful, CAS MUST then invalidate + the ticket, causing all future validation attempts of that same ticket to + fail. + +- CAS SHOULD expire unvalidated proxy tickets in a reasonable period + of time after they are issued. If a service presents for validation an + expired proxy ticket, CAS MUST respond with a validation failure response. + +- It is RECOMMENDED that the validation response include a descriptive message + explaining why validation failed. + +- It is RECOMMENDED that the duration a proxy ticket is valid before it expires + be no longer than five minutes. Local security and CAS usage considerations + MAY determine the optimal lifespan of unvalidated proxy tickets. + +- Proxy tickets MUST contain adequate secure random data so that a ticket is + not guessable. + +- Proxy tickets SHOULD begin with the characters, `PT-`. + +- Back-end services MUST be able to accept proxy tickets of up to 32 characters + in length. + +- It is RECOMMENDED that back-end services support proxy tickets of up to 256 + characters in length. + + + + + +**3.3. proxy-granting ticket** +------------------------------ + +A proxy-granting ticket (PGT) is an opaque string that is used by a service to obtain +proxy tickets for obtaining access to a back-end service on behalf of a client. +Proxy-granting tickets are obtained from CAS upon validation of a service ticket +or a proxy ticket. Proxy-granting ticket issuance is described fully in Section +[2.5.4](<#head2.5.4>). + + + + + +### **3.3.1. proxy-granting ticket properties** + +- Proxy-granting tickets MAY be used by services to obtain multiple proxy + tickets. Proxy-granting tickets are not one-time-use tickets. + +- Proxy-granting tickets MUST expire when the client whose authentication is + being proxied logs out of CAS. + +- Proxy-granting tickets MUST contain adequate secure random data so that a + ticket is not guessable in a reasonable period of time through brute-force + attacks. + +- Proxy-granting tickets SHOULD begin with the characters `PGT-`. + +- Services MUST be able to handle proxy-granting tickets of up to 64 + characters in length. + +- It is RECOMMENDED that services support proxy-granting tickets of up to + 256 characters in length. + + + + + +**3.4. proxy-granting ticket IOU** +---------------------------------- + +A proxy-granting ticket IOU is an opaque string that is placed in the response +provided by `/serviceValidate` and `/proxyValidate` used to correlate a service +ticket or proxy ticket validation with a particular proxy-granting ticket. See +Section [2.5.4](<#head2.5.4>) for a full description of this process. + + + + + +### **3.4.1. proxy-granting ticket IOU properties** + +- Proxy-granting ticket IOUs SHOULD NOT contain any reference to their + associated proxy-granting tickets. Given a particular PGTIOU, it MUST NOT be + possible to derive its corresponding PGT through algorithmic methods in a + reasonable period of time. + +- Proxy-granting ticket IOUs MUST contain adequate secure random data so that + a ticket is not guessable in a reasonable period of time through brute-force + attacks. + +- Proxy-granting ticket IOUs SHOULD begin with the characters, `PGTIOU-`. + +- Services MUST be able to handle PGTIOUs of up to 64 characters in length. It + is RECOMMENDED that services support PGTIOUs of up to 256 characters in + length. + + + + + +**3.5. login ticket** +--------------------- + +A login ticket is a string that is provided by `/login` as a credential requester +and passed to `/login` as a credential acceptor for username/password +authentication. Its purpose is to prevent the replaying of credentials due to +bugs in web browsers. + + + + + +### **3.5.1. login ticket properties** + +- Login tickets issued by `/login` MUST be probabilistically unique. + +- Login tickets MUST only be valid for one authentication attempt. Whether or + not authentication was successful, CAS MUST then invalidate the login + ticket, causing all future authentication attempts with that instance of + that login ticket to fail. + +- Login tickets SHOULD begin with the characters `LT-`. + + + +**3.6. ticket-granting cookie** +------------------------------- + +A ticket-granting cookie is an HTTP cookie[[5](<#5>)] set by CAS upon the +establishment of a single sign-on session. This cookie maintains login state for +the client, and while it is valid, the client can present it to CAS in lieu of +primary credentials. Services can opt out of single sign-on through the `renew` +parameter described in Sections [2.1.1](<#head2.1.1>), [2.4.1](<#head2.4.1>), +and [2.5.1](<#head2.5.1>). + + + + + +### **3.6.1. ticket-granting cookie properties** + +- A ticket-granting cookie SHALL be set to expire at the end of the client's + browser session if Long-Term support is not active ([4.1.1](<#head4.1.1>)) + for the corresponding TGT. + +- CAS SHALL set the cookie path to be as restrictive as possible. For example, + if the CAS server is set up under the path /cas, the cookie path SHALL be set + to /cas. + +- The value of ticket-granting cookies SHALL contain adequate secure random data + so that a ticket-granting cookie is not guessable in a reasonable period of time. + +- The name of ticket-granting cookies SHOULD begin with the characters `TGC-`. + +- The value of ticket-granting cookies SHOULD follow the same rules as the ticket-granting + ticket. Typically, the value of the ticket-granting cookies MAY contain the ticket-granting + ticket itself as the representation of the authenticated single sign-on session. + + + +**3.7. ticket and ticket-granting cookie character set** +-------------------------------------------------------- + +In addition to the above requirements, all CAS tickets and the value of the +ticket-granting cookie MUST contain only characters from the set `{A-Z, a-z, 0-9}`, +and the hyphen character `-`. + + + + +**3.8. ticket-granting ticket** +------------------------------ + +A ticket-granting ticket (TGT) is an opaque string that is generated by the CAS +server that is issued upon an successful authentication event upon `/login`. +This ticket may be tied to the ticket-granting cookie which represents the +state of the single sign-on session, with validity period and acts as the +foundation and baseline for issuance of service tickets, proxy-granting +tickets, and more. + + + +### **3.8.1. ticket-granting ticket properties** + +- Ticket-granting tickets MAY be used by services to obtain multiple service + tickets. Ticket-granting tickets are not one-time-use tickets and are associated + with a validity period and expiration policy. + +- Ticket-granting tickets MUST expire when the client whose authentication is + being managed logs out of CAS. + +- Ticket-granting tickets MUST contain adequate secure random data so that a + ticket is not guessable in a reasonable period of time through brute-force + attacks. + +- Ticket-granting tickets SHOULD begin with the characters `TGT-`. + +- It is RECOMMENDED that ticket-granting tickets be encrypted when + shared with other external resources in order to minimize security + vulnerabilities as they are tied to the ticket-granting cookie + and represent the authentication session. + + + + +**4. Optional Features** +======================== + + + +**4.1 Long-Term Tickets - Remember-Me [CAS 3.0]** +------------------------------------------------- + +CAS Server MAY support Long-Term Ticket Granting Tickets (referred to as +"Remember Me" functionality). If this feature is supported by the CAS Server, it +is possible to perform recurring, non interactive re-logins to the CAS Server as +long as the Long-Term Ticket Granting Ticket in the CAS Server is not expired +and the browsers TGC Cookie is valid. + + + + +### **4.1.1 Enabling Remember-Me (Login Page)** + +- The CAS Server MUST provide a checkbox on the login page to allow Remember-Me +functionality. + +- By default, the checkbox MUST be unchecked. + +- It MUST be the users choice to enable Remember-Me for the login or not. + See Section [2.2.2](<#head2.2.2>). + + + + + +### **4.1.2 Security implications** + +Enabling Remember-Me MAY have security implications. As the CAS authentication +is bound to the browser and the user is not getting interactively logged-in when +a valid Long-Term TGT ticket exists and the CAS cookie presented by the browser +is valid, special care MUST be taken on the CAS client side to handle Remember-Me +logins properly. It MUST be the CAS clients responsibility to decide +if and when Remember-Me CAS logins might be handled special. See [4.1.3](<#head4.1.3>). + + + + + +### **4.1.3 CAS Validation Response Attributes** + +As only the CAS Client MUST decide how to handle Remember-Me logins (see +[4.2.1](<#head4.2.1>)), the CAS Server MUST provide information about a +Remember-Me login to the CAS Client. This information MUST be provided by all +ticket validation methods supported by the CAS Server (See Sections +[2.5](<#head2.5>), [2.6](<#head2.6>) and [2.8](<#head2.8>)) in this case. + +- In serviceValidate XML responses (see [Appendix A](<#head_appdx_a>)), a +Remember-Me login MUST be indicated by the +`longTermAuthenticationRequestTokenUsed` attribute. Additionally, the +`isFromNewLogin` attribute MAY be used to decide whether this has security +implications. + +- In SAML validation responses, Remember-Me MUST be indicated by the +`longTermAuthenticationRequestTokenUsed` attribute. + + + + + +### **4.1.4 CAS Client requirements** + +If the CAS client needs to handle Remember-Me logins special (e.g. deny access to +sensitive areas of the CAS client application on a remembered login), the CAS +client MUST NOT use the `/validate` CAS validation URL, as this URL does not +support CAS attributes in the validation response document. + + +### **4.1.5 Long-Term ticket-granting cookie properties** + +When a Long-Term TGT was created by the CAS Server, the Ticket-granting cookie +MUST NOT expire at the end of the client's browser session as defined in [3.6.1](<#head3.6.1>). +Instead, the Ticket Granting cookie SHALL expire at the defined Long-Term TGT ticket lifetime. + +The lifetime value definition of Long-Term Ticket Granting Tickets is up to the CAS Server implementer. +The Long-Term Ticket Granting Ticket lifetime MAY not exceed 3 months. + + + + +**4.2 /samlValidate [CAS 3.0]** +------------------------------- + +`/samlValidate` checks the validity of a Service Ticket by a SAML 1.1 request +document provided by a HTTP POST. A SAML (Secure Access Markup Language)[7](#7) 1.1 +response document MUST be returned. This allows the release of additional +information (attributes) of the authenticated NetID. The Security Assertion +Markup Language (SAML) describes a document and protocol framework by which +security assertions (such as assertions about a prior act of authentication) can +be exchanged. + + + + + +### **4.2.1 parameters** + +The following HTTP request parameters MUST be specified to `/samlValidate`. They +are both case-sensitive. + +- `TARGET` [REQUIRED] - URL encoded service identifier of the back-end service. + Note that as an HTTP request parameter, this URL value MUST be URL-encoded + as described in Section 2.2 of RFC 1738[[4]](#4). The service identifier + specified here MUST match the `service` parameter provided to `/login`. See + Section [2.1.1](<#head2.1.1>). The `TARGET` service SHALL use HTTPS. SAML + attributes MUST NOT be released to a non-SSL site. + + + + + +### **4.2.2 HTTP Request Method and Body** + +Request to /samlValidate MUST be a HTTP POST request. The request body MUST be a valid +SAML 1.0 or 1.1 request XML document of document type "text/xml". + + + + + +### **4.2.3 SAML request values** + +- `RequestID` [REQUIRED] - unique identifier for the request + +- `IssueInstant` [REQUIRED] - timestamp of the request + +- `samlp:AssertionArtifact` [REQUIRED] - the valid CAS Service Ticket obtained as a response + parameter at login. See section [2.2.4](<#head2.2.4>). + + + + + +### **4.2.4 Example of /samlValidate POST request** + +{% highlight bash %} +POST /cas/samlValidate?ticket= +Host: cas.example.com +Content-Length: 491 +Content-Type: text/xml +{% endhighlight %} + +{% highlight xml %} + + + + + + ST-1-u4hrm3td92cLxpCvrjylcas.example.com + + + + +{% endhighlight %} + + + + +### **4.2.5 SAML response** + +CAS Server response to a `/samlValidate` request. MUST be a SAML 1.1 response. + +Example SAML 1.1 validation response: + +{% highlight xml %} + + + + + + + + + + + + + + https://some-service.example.com/app/ + + + + + + johnq + + + urn:oasis:names:tc:SAML:1.0:cm:artifact + + + + + 12345 + + + + uugid=middleware.staff,ou=Groups,dc=vt,dc=edu + + + + staff + + + ACTIVE + + + + + johnq + + + urn:oasis:names:tc:SAML:1.0:cm:artifact + + + + + + + + +{% endhighlight %} + + + + +### **4.2.5.1 SAML CAS response attributes** + +The following additional attributes might be provided within the SAML response: + +- `longTermAuthenticationRequestTokenUsed` - If Long Term Ticket Granting +Tickets (Remember-Me) are supported by the CAS Server (see Section [4.1](<#head4.1>)), the +SAML response MUST include this attribute to indicate remembered logins +to the CAS client. + + + + +**Appendix A: CAS response XML schema** +======================================= + +{% highlight xml %} + + + + The following is the schema for the Central Authentication Service (CAS) version 3.0 protocol response. This covers the responses for the following servlets: /serviceValidate, /proxyValidate, /p3/serviceValidate, /p3/proxyValidate, /proxy This specification is subject to change. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true if a long-term (Remember-Me) token was used + + + + + true if this was from a new, interactive login. If login was from a non-interactive login (e.g. Remember-Me), this value is false or might be omitted. + + + + + One or many elements describing the units the user is member in. E.g. LDAP format values. + + + + + Any user specific attribute elements. + + + + + +{% endhighlight %} + +> Note: As user attributes can be extended by the CAS Server implementer (see +> \ schema definition), it is RECOMMENDED to form custom attributes +> as using the following format: + +{% highlight xml %} + + VALUE + +{% endhighlight %} + + + +**Appendix B: Safe redirection** +================================ + +After a successful login, safely redirecting the client from CAS to its final +destination must be handled with care. In most cases, the client has sent +credentials to the CAS server over a POST request. By this specification, the +CAS server must then forward the user to the application with a GET request. + +The HTTP/1.1 RFC[[3](<#3>)] provides a response code of 303: See Other, which +provides for the desired behavior: a script that receives data through a POST +request can, through a 303 redirection, forward the browser to another URL +through a GET request. However, not all browsers have implemented this behavior +correctly. + +The RECOMMENDED method of redirection is thus JavaScript. A page containing a +`window.location.href` in the following manner performs adequately: + +{% highlight html %} + + + Yale Central Authentication Service + + + + + + +{% endhighlight %} + + +Additionally, CAS should disable browser caching by setting all of the various +cache-related headers: + +- Pragma: no-cache + +- Cache-Control: no-store + +- Expires: [RFC 1123[6] date equal to or before now] + +The introduction of the login ticket removed the possibility of CAS accepting +credentials that were cached and replayed by a browser. However, early versions +of Apple's Safari browser contained a bug where through usage of the Back +button, Safari could be coerced into presenting the client's credentials to the +service it is trying to access. CAS can prevent this behavior by not +automatically redirecting if it detects that the remote browser is one of these +early versions of Safari. Instead, CAS should display a page that states login +was successful, and provide a link to the requested service. The client must +then manually click to proceed. + + + + + +**Appendix C: Logout XML document** +=================================== + +When SLO is supported by the CAS Server, it will callback to each of the +services that are registered with the system and send a POST request with the +following SAML Logout Request XML document: + + +{% highlight xml %} + + + @NOT_USED@ + + [SESSION IDENTIFIER] + ` +{% endhighlight %} + + + + +**Appendix D: References** +========================== + +[1] Bradner, S., "Key words for use in RFCs to Indicate Requirement +Levels", [RFC 2119](), Harvard University, +March 1997. + +[2] Berners-Lee, T., Fielding, R., Frystyk, H., "Hypertext Transfer +Protocol - HTTP/1.0", [RFC 1945](), +MIT/LCS, UC Irvine, MIT/LCS, May 1996. + +[3] Fielding, R., Gettys, J., Mogul, J., Frystyk, H., Masinter, L., +Leach, P., Berners-Lee, T., "Hypertext Transfer Protocol - HTTP/1.1", [RFC +2068](), UC Irvine, Compaq/W3C, Compaq, +W3C/MIT, Xerox, Microsoft, W3C/MIT, June 1999. + +[4] Berners-Lee, T., Masinter, L., and MaCahill, M., "Uniform +Resource Locators (URL)", [RFC 1738](), +CERN, Xerox Corporation, University of Minnesota, December 1994. + +[5] Kristol, D., Montulli, L., "HTTP State Management Mechanism", +[RFC 2965](), Bell Laboratories/Lucent +Technologies, Epinions.com, Inc., October 2000. + +[6] Braden, R., "Requirements for Internet Hosts - Application and +Support", [RFC 1123](), Internet +Engineering Task Force, October 1989. + +[7] OASIS SAML 1.1 standard, saml.xml.org, December 2009. + +[8] Apereo [CAS Server]() reference +implementation + + + + +**Appendix E: CAS License** +=========================== + +Licensed to Apereo under one or more contributor license agreements. See the +NOTICE file distributed with this work for additional information regarding +copyright ownership. Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at the following location: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + + +**Appendix F: YALE License** +=========================== + +Copyright (c) 2000-2005 Yale University. All rights reserved. + +THIS SOFTWARE IS PROVIDED "AS IS," AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE, ARE EXPRESSLY DISCLAIMED. IN NO EVENT SHALL +YALE UNIVERSITY OR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED, THE +COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED IN ADVANCE OF THE POSSIBILITY OF SUCH DAMAGE. + +Redistribution and use of this software in source or binary forms, with or +without modification, are permitted, provided that the following conditions are +met: + +1. Any redistribution must include the above copyright notice and disclaimer + and this list of conditions in any related documentation and, if feasible, + in the redistributed software. + +2. Any redistribution must include the acknowledgment, "This product includes + software developed by Yale University," in any related documentation and, if + feasible, in the redistributed software. + +3. The names "Yale" and "Yale University" must not be used to endorse or + promote products derived from this software. + + + + + +**Appendix F: Changes to this Document** +======================================== + +May 4, 2005: v1.0 - initial release + +March 2, 2012: v1.0.1 - fixed "noscropt" typo. apetro per amazurek with credit +to Faraz Khan at ASU for catching the typo. + +April, 2013: v2.0 - CAS 3.0 protocol, Apereo copyright, Apache License 2.0 diff --git a/cas-server-documentation/protocol/CAS-Protocol.md b/cas-server-documentation/protocol/CAS-Protocol.md new file mode 100644 index 000000000000..b27811b6e065 --- /dev/null +++ b/cas-server-documentation/protocol/CAS-Protocol.md @@ -0,0 +1,51 @@ +--- +layout: default +title: CAS - CAS Protocol +--- + + +# CAS protocol +The CAS protocol is a simple and powerful ticket-based protocol developed exclusively for CAS. A complete protocol specification may be found [here](CAS-Protocol-Specification.html). + +It involves one or many clients and one server. Clients are embedded in *CASified* applications (called "CAS services") whereas the CAS server is a standalone component: + +- The [CAS server](../installation/Configuring-Authentication-Components.html) is responsible for authenticating users and granting accesses to applications +- The [CAS clients](../integration/CAS-Clients.html) protect the CAS applications and retrieve the identity of the granted users from the CAS server. + +The key concepts are: + +- The TGT (Ticket Granting Ticket), stored in the CASTGC cookie, represents a SSO session for a user +- The ST (Service Ticket), transmitted as a GET parameter in urls, stands for the access granted by the CAS server to the *CASified* application for a specific user. + + +## Versions +The current CAS protocol is the version 3.0. The draft version of the protocol is available as [part of the CAS codebase](https://github.com/Jasig/cas/blob/master/cas-server-protocol/3.0/cas_protocol_3_0.md), which is hereby implemented. It's mainly a capture of the most common enhancements built on top of the CAS protocol revision 2.0. Among all features, the most noticeable update between versions 2.0 and 3.0 is the ability to return the authentication/user attributes through the new `/p3/serviceValidate` response (in addition to the `/serviceValidate` endpoint, already existing for CAS 2.0 protocol). + + +## Web flow diagram + +CAS Web flow diagram + + +## Proxy web flow diagram +One of the most powerful feature of the CAS protocol is the ability for a CAS service to act as a proxy for another CAS service, transmitting the user identity. + +CAS Proxy web flow diagram + + + + +## Other protocols +Even if the primary goal of the CAS server is to implement the CAS protocol, other protocols are also supported as extensions: + +- [OpenID](../protocol/OpenID-Protocol.html) +- [OAuth](../protocol/OAuth-Protocol.html) +- [SAML](../protocol/SAML-Protocol.html) + +*** + + + +#Delegated Authentication +Using the CAS protocol, the CAS server can also be configured to [delegate the authentication](../integration/Delegate-Authentication.html) to another CAS server. + diff --git a/cas-server-documentation/protocol/OAuth-Protocol.md b/cas-server-documentation/protocol/OAuth-Protocol.md new file mode 100644 index 000000000000..1292b5e78a91 --- /dev/null +++ b/cas-server-documentation/protocol/OAuth-Protocol.md @@ -0,0 +1,27 @@ +--- +layout: default +title: CAS - OAuth Protocol +--- + +#OAuth Protocol +You can configure the CAS server with: + +* [OAuth client support](../integration/Delegate-Authentication.html), which means authentication can be delegated through a link on the login page to a CAS, OpenID or OAuth provider. +* [OAuthn server support](../installation/OAuth-OpenId-Authentication.html), which means you will be able to communicate with your CAS server through the [OAuth 2.0 protocol](http://oauth.net/2/), using the *Authorization Code* grant type. + +#CAS OAuth Server Support +Three new urls will be available: + +* **/oauth2.0/authorize** +It's the url to call to authorize the user: the CAS login page will be displayed and the user will authenticate. After successful authentication, the user will be redirected to the OAuth *callback url* with a code. Input GET parameters required: *client_id* and *redirect_uri*. + +* **/oauth2.0/accessToken** +It's the url to call to exchange the code for an access token. Input GET parameters required: *client_id*, *redirect_uri*, *client_secret* and *code*. + +* **/oauth2.0/profile** +It's the url to call to get the profile of the authorized user. Input GET parameter required: *access_token*. The response is in JSON format with all attributes of the user. + +#Delegate to an OAuth Provider + +Using the OAuth protocol, the CAS server can also be configured to [delegate the authentication](../integration/Delegate-Authentication.html) to an OAuth provider (like Facebook, Twitter, Google, Yahoo...) + diff --git a/cas-server-documentation/protocol/OpenID-Protocol.md b/cas-server-documentation/protocol/OpenID-Protocol.md new file mode 100644 index 000000000000..30018cf3f83d --- /dev/null +++ b/cas-server-documentation/protocol/OpenID-Protocol.md @@ -0,0 +1,238 @@ +--- +layout: default +title: CAS - OpenID Protocol +--- + +#OpenID Protocol +OpenID is an open, decentralized, free framework for user-centric digital identity. Users represent themselves using URIs. For more information see the [http://www.openid.net](http://www.openid.net). + +CAS supports both the "dumb" and "smart" modes of the OpenID protocol. Dumb mode acts in a similar fashion to the existing CAS protocol. The smart mode differs in that it establishes an association between the client and the openId provider (OP) at the begining. Thanks to that association and the key exchange done during association, information exchanged between the client and the provider are signed and verified using this key. There is no need for the final request (which is equivalent in CAS protocol to the ticket validation). + +OpenID identifiers are URIs. The default mechanism in CAS support is an uri ending with the actual user login (ie. http://my.cas.server/openid/*myusername* where the actual user login is *myusername*). This is not recommended and you should think of a more elaborated way of providing URIs to your users. + +Support is enabled by including the following dependency in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-support-openid + ${cas.version} + +{% endhighlight %} + +##Configuration + +###Declare the OpenID endpoint + +The OpenID discovery endpoint should be enabled during the configuration process. In the *`web.xml`* file, the following mapping must be added: + +{% highlight xml %} + + cas + /openid/* + +{% endhighlight %} + +In the *cas-servlet.xml* file, the following mapping and bean must be also added: + +{% highlight xml %} + + + + logoutController + ... + openIdProviderController + ... + + +{% endhighlight %} + + +###Add the OpenID entry in the unique id generator map + +The OpenID entry should be added to the *`uniqueIdGenerators.xml`* file: + +{% highlight xml %} + + ... + + +{% endhighlight %} + + +###Update the webflow + +CAS uses a spring webflow to describe the the authentication process. We need to change it a little bit to allow CAS to switch to OpenID authentication if it recognizes one. This is done in the *`login-webflow.xml`* file. After the on-start element just add these two blocks: + +{% highlight xml %} + + + + + + + + + + + + +{% endhighlight %} + +The `openIdSingleSignOnAction` is itself defined in the *cas-servlet.xml* file: + +{% highlight xml %} + +{% endhighlight %} + + +###Enable OpenID in the AuthenticationManager + +The authentication manager is the place where authentication takes place. We must provide it two elements needed for a successful OpenId authentication. The first thing to do is to detect the user name from the OpenID identifier. When your CAS server will work as an OP, users will authenticate with an OpenID identifier, looking like this : `http://localhost:8080/cas/openid/*myusername*`. Actually, in your users database, this users login is probably `myusername`. We must provide the CAS server with a way to extract the user principal from the credentials he provides us. This is the first thing we'll do in this section: add an `OpenIdCredentialsToPrincipalResolver` to the authentication manager. The next thing to give CAS is a specialized authentication handler. + +Open the *`deployerConfigContext.xml`* file, and locate the `authenticationManager` bean definition. It has two properties containing beans. In the credentials to principal property, add this bean definition: + +{% highlight xml %} + + +{% endhighlight %} + +Then, in the authentication handler property, add this bean definition: + +{% highlight xml %} + + +{% endhighlight %} + + +###Adapt the Spring CAS servlet configuration + +We now have to make CAS handle the OpenID request that is presented. First, we'll add a handler for the `/login` url, when called to validate a ticket (CAS is implementing the dumb OpenID mode, which means it does not create an association at the beginning of the authentication process. It must then check the received authentication success notification, which is done by one extra HTTP request at the end of the process). Anywhere in the *`cas-servlet.xml`* file, add this bean definition: + +{% highlight xml %} + + + + + + delegatingController + + + +{% endhighlight %} + +As we gave the order of 2 to the `OpenIdPostUrlHandlerMapping`, we must modify the `FlowHandlerMapping` objects orders to give it an incremented value. The `loginFlowHandlerMapping` order is incremented from 2 to 3: + +{% highlight xml %} + + + + + +{% endhighlight %} + +And the `logoutFlowHandlerMapping` order is moved from 3 to 4: + +{% highlight xml %} + + + + + +{% endhighlight %} + +In the `handlerMappingOpenId`, we referenced a bean called `delegatingController`. This bean delegates the processing of a request to the first controller of its delegates which says it can handle it. So now we'll provide two delegate controllers. The first one is handling the Smart OpenId association, and the second process the authentication and ticket validation. Add this two beans in the file. + +The Smart OpenId controller: + +{% highlight xml %} + +{% endhighlight %} + +The OpenID validation controller: + +{% highlight xml %} + +{% endhighlight %} + +We are done with the delegates. Now we must create the Delegating controller itself, and give it a list of delegates referencing the two delegates we just defined. So add this definition: + +{% highlight xml %} + + + + + + + +{% endhighlight %} + +Don't forget to include the *`util`* namespace if you don't have it already. + + +###Add an argument extractor + +We must tell cas how to extract the OpenID information from the authentication request (`openid.mode`, `openid.sig`, `openid.assoc_handle`, etc). This is done in the *`argumentExtractorsConfiguration.xml`* file, located in the *`spring-configuration`* directory. Add this bean into the file: + +{% highlight xml %} + + + + + + + + +{% endhighlight %} + + +###Add the server manager + +Next we must provide a ServerManager, which is a class from the [`openid4java` library](https://code.google.com/p/openid4java/), which allows us to handle the Diffie-Hellman algorithm used by the association process. In the *`spring-configuration/applicationContext.xml`* file, add this bean definition: + +{% highlight xml %} + +{% endhighlight %} + +And finally, we need an applicationContext provider in the *`spring-configuration/applicationContext.xml`* file again: + +{% highlight xml %} + +{% endhighlight %} + +*** + +#Delegate To an OpenID Provider + +Using the OpenID protocol, the CAS server can also be configured to [delegate the authentication](../integration/Delegate-Authentication.html) to an OpenID provider. + diff --git a/cas-server-documentation/protocol/REST-Protocol-Deprecated.md b/cas-server-documentation/protocol/REST-Protocol-Deprecated.md new file mode 100644 index 000000000000..72a13bf2240b --- /dev/null +++ b/cas-server-documentation/protocol/REST-Protocol-Deprecated.md @@ -0,0 +1,95 @@ +--- +layout: default +title: CAS - CAS REST Protocol (Deprecated) +--- + +#REST Protocol +The REST protocol allows one to model applications as users, programmatically acquiring service tickets to authenticate to other applications. This means that other applications would be able to use a CAS client to accept Service Tickets rather than to rely upon another technology such as client SSL certificates for application-to-application authentication of requests. This is achieved by exposing a way to RESTfully obtain a Ticket Granting Ticket and then use that to obtain a Service Ticket. + +
Deprecated Module!

Note that the instructions in this document refer to a deprecated REST module. Please use this document instead if you plan to turn on the CAS server's REST API.

+ +
Usage Warning!

The REST endpoint may become a tremendously convenient target for brute force dictionary attacks on CAS server. Enable support only soberly and with due consideration of security aspects.

+ +#Components +By default the CAS RESTful API is configured in the `restlet-servlet.xml`, which contains the routing for the tickets. It also defines the resources that will resolve the URLs. The `TicketResource` defined by default (which can be extended) accepts username/password. + +Support is enabled by including the following in your `pom.xml` file: + +{% highlight xml %} + + org.jasig.cas + cas-server-integration-restlet + ${cas.version} + +{% endhighlight %} + +REST support is currently provided internally by the [Restlet framework](http://restlet.org/‎). + + +#Configuration +To turn on the protocol, add the following to the `web.xml`: + +{% highlight xml %} + + restlet + org.restlet.ext.spring.RestletFrameworkServlet + 1 + + + + restlet + /v1/* + + +{% endhighlight %} + +#Protocol + +##Request a Ticket Granting Ticket + +###Sample Request +{% highlight bash %} +POST /cas/v1/tickets HTTP/1.0 + +username=battags&password=password&additionalParam1=paramvalue +{% endhighlight %} + + +###Sample Response + + +####Successful Response +{% highlight bash %} +201 Created +Location: http://www.whatever.com/cas/v1/tickets/{TGT id} +{% endhighlight %} + + +####Unsuccessful Response +If incorrect credentials are sent, CAS will respond with a 400 Bad Request error (will also respond for missing parameters, etc.). If you send a media type it does not understand, it will send the 415 Unsupported Media Type. + + +##Request a Service Ticket + +###Sample Request +{% highlight bash %} +POST /cas/v1/tickets/{TGT id} HTTP/1.0 + +service={form encoded parameter for the service url} +{% endhighlight %} + +###Sample Response + +####Successful Response +{% highlight bash %} +200 OK +ST-1-FFDFHDSJKHSDFJKSDHFJKRUEYREWUIFSD2132 +{% endhighlight %} +####Unsuccessful Response +CAS will send a 400 Bad Request. If an incorrect media type is sent, it will send the 415 Unsupported Media Type. + + +##Logout +{% highlight bash %} +DELETE /cas/v1/tickets/TGT-fdsjfsdfjkalfewrihfdhfaie HTTP/1.0 +{% endhighlight %} diff --git a/cas-server-documentation/protocol/REST-Protocol.md b/cas-server-documentation/protocol/REST-Protocol.md new file mode 100644 index 000000000000..3887c1c6a284 --- /dev/null +++ b/cas-server-documentation/protocol/REST-Protocol.md @@ -0,0 +1,106 @@ +--- +layout: default +title: CAS - CAS REST Protocol +--- + +# REST Protocol +The REST protocol allows one to model applications as users, programmatically acquiring service tickets to authenticate to other applications. This means that other applications would be able to use a CAS client to accept Service Tickets rather than to rely upon another technology such as client SSL certificates for application-to-application authentication of requests. This is achieved by exposing a way to RESTfully obtain a Ticket Granting Ticket and then use that to obtain a Service Ticket. + +
Usage Warning!

The REST endpoint may become a tremendously convenient target for brute force dictionary attacks on CAS server. Enable support only soberly and with due consideration of security aspects.

+ +
Restlet Module

If you are looking for the Restlet implementation of the CAS REST API, you will find the instructions here in this document.

+ +# Components +By default the CAS REST API is configured to add routing for the tickets. It also defines the resources that will resolve the URLs. The `TicketResource` defined by default (which can be extended) accepts username/password. + +Support is enabled by including the following in your `pom.xml` file: + + +{% highlight xml %} + + org.jasig.cas + cas-server-support-rest + ${cas.version} + runtime + +{% endhighlight %} + +REST support is currently provided internally by the [Spring framework](http://spring.io/guides/gs/rest-service/‎). + + +#Configuration +To turn on the protocol, add the following to the `web.xml`: + +{% highlight xml %} + + cas + /v1/* + +{% endhighlight %} + + +...or delete the `web.xml` in the overlay altogether if there are no other customizations there as this mapping is provided by CAS' webapp module's `web.xml` out of the box. + +Please note that if there are local customizations in overlay's `web.xml`, the following `contextConfigLocation` `` must also be added in order to enable the new REST module: `classpath*:/META-INF/spring/*.xml`. So the entire context-param block would look like this: + +{% highlight xml %} + + contextConfigLocation + + /WEB-INF/spring-configuration/*.xml + /WEB-INF/deployerConfigContext.xml + classpath*:/META-INF/spring/*.xml + + +{% endhighlight %} + +#Protocol + +##Request a Ticket Granting Ticket + +###Sample Request +{% highlight bash %} +POST /cas/v1/tickets HTTP/1.0 + +username=battags&password=password&additionalParam1=paramvalue +{% endhighlight %} + + +###Sample Response + + +####Successful Response +{% highlight bash %} +201 Created +Location: http://www.whatever.com/cas/v1/tickets/{TGT id} +{% endhighlight %} + + +####Unsuccessful Response +If incorrect credentials are sent, CAS will respond with a 400 Bad Request error (will also respond for missing parameters, etc.). If you send a media type it does not understand, it will send the 415 Unsupported Media Type. + + +##Request a Service Ticket + +###Sample Request +{% highlight bash %} +POST /cas/v1/tickets/{TGT id} HTTP/1.0 + +service={form encoded parameter for the service url} +{% endhighlight %} + +###Sample Response + +####Successful Response +{% highlight bash %} +200 OK +ST-1-FFDFHDSJKHSDFJKSDHFJKRUEYREWUIFSD2132 +{% endhighlight %} +####Unsuccessful Response +CAS will send a 400 Bad Request. If an incorrect media type is sent, it will send the 415 Unsupported Media Type. + + +##Logout +{% highlight bash %} +DELETE /cas/v1/tickets/TGT-fdsjfsdfjkalfewrihfdhfaie HTTP/1.0 +{% endhighlight %} diff --git a/cas-server-documentation/protocol/SAML-Protocol.md b/cas-server-documentation/protocol/SAML-Protocol.md new file mode 100644 index 000000000000..814d4334182f --- /dev/null +++ b/cas-server-documentation/protocol/SAML-Protocol.md @@ -0,0 +1,196 @@ +--- +layout: default +title: CAS - CAS SAML Protocol +--- + +#SAML Protocol +CAS has support for versions 1.1 and 2 of the SAML protocol to a specific extent. This document deals with CAS-specific concerns. + +Support is enabled by including the following dependency in the Maven WAR overlay: + +{% highlight xml %} + + org.jasig.cas + cas-server-support-saml + ${cas.version} + +{% endhighlight %} + +#SAML 1.1 +CAS supports the [standardized SAML 1.1 protocol](http://en.wikipedia.org/wiki/SAML_1.1) primarily to: + +- Support a method of [attribute release](../integration/Attribute-Release.html) +- [Single Logout](../installation/Logout-Single-Signout.html) + +A SAML 1.1 ticket validation response is obtained by validating a ticket via POST at the `/samlValidate URI`. + + +##Sample Request +{% highlight xml %} +POST /cas/samlValidate?ticket= +Host: cas.example.com +Content-Length: 491 +Content-Type: text/xml + + + + + + + ST-1-u4hrm3td92cLxpCvrjylcas.example.com + + + + +{% endhighlight %} + + +##Sample Response +{% highlight xml %} + + + + + + + + + + + + + https://some-service.example.com/app/ + + + + + + johnq + + + urn:oasis:names:tc:SAML:1.0:cm:artifact + + + + + 12345 + + + + uugid=middleware.staff,ou=Groups,dc=vt,dc=edu + + + + staff + + + ACTIVE + + + + + johnq + + + urn:oasis:names:tc:SAML:1.0:cm:artifact + + + + + + + + +{% endhighlight %} + + +##Configuration + +In addition to the `cas-server-support-saml` module dependency, the following steps are required to enabled the SAML 1.1 support. + +###Definition/Mapping of `samlValidateController` + +In `cas-servlet.xml`: + +{% highlight xml %} + + + + + + ... + samlValidateController + ... +{% endhighlight %} + +###Servlet mapping for `/samlValidate` + +In the `web.xml` file: + +{% highlight xml %} + + cas + /samlValidate + +{% endhighlight %} + +###SAML Argument Extractor + +In the `argumentExtractorsConfiguration.xml` file: + +{% highlight xml %} + + + + + + +{% endhighlight %} + +###SAML ID Generator + +In the uniqueIdGenerators.xml file: + +{% highlight xml %} + + + + + + + + +{% endhighlight %} + +###SAML Views +In `cas-servlet.xml`, uncomment the following: + +{% highlight xml %} + +{% endhighlight %} + +#SAML 2 + +CAS support for SAML 2 at this point is mostly limited to [Google Apps Integration](../integration/Google-Apps-Integration.html). Full SAML 2 support can also be achieved via Shibboleth with CAS handling the authentication and SSO. [See this guide](../integration/Shibboleth.html) for more info. + diff --git a/cas-server-documentation/sidebar.html b/cas-server-documentation/sidebar.html new file mode 100644 index 000000000000..cb4f3ea2cc73 --- /dev/null +++ b/cas-server-documentation/sidebar.html @@ -0,0 +1,73 @@ + + + +

Planning

+ + +

Installation

+ + +

Integration

+ + +

Developer

+ diff --git a/cas-server-extension-clearpass/NOTICE b/cas-server-extension-clearpass/NOTICE new file mode 100644 index 000000000000..5a9d037dea5d --- /dev/null +++ b/cas-server-extension-clearpass/NOTICE @@ -0,0 +1,119 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + "Java Concurrency in Practice" book annotations under Creative Commons Attribution License + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apache Santuario under The Apache Software License, Version 2.0 + Apache Velocity under The Apache Software License, Version 2.0 + Apereo CAS ClearPass Extension - DEPRECATED under Apache 2 + Apereo CAS Core under Apache 2 + Apereo CAS Ehcache Integration under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + CDI APIs under Apache License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + ehcache under The Apache Software License, Version 2.0 + Ehcache JCache Implementation under The Apache Software License, Version 2.0 + ESAPI 2.0 under BSD or Creative Commons 3.0 BY-SA + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + HttpClient under Apache License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Jasig CAS Client for Java - Core under Apache License Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Lang under The Apache Software License, Version 2.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Not Yet Commons SSL under Apache License v2 + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + OpenSAML-J under Apache 2 + OpenWS under Apache 2 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + Xalan Java under The Apache Software License, Version 2.0 + Xalan Java Serializer under The Apache Software License, Version 2.0 + Xerces2-j under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + XML Commons Resolver Component under The Apache Software License, Version 2.0 + XMLTooling-J under Apache 2 + diff --git a/cas-server-extension-clearpass/pom.xml b/cas-server-extension-clearpass/pom.xml new file mode 100644 index 000000000000..b977fa0cf3b3 --- /dev/null +++ b/cas-server-extension-clearpass/pom.xml @@ -0,0 +1,109 @@ + + + + 4.0.0 + + cas-server + org.jasig.cas + 4.1.0-SNAPSHOT + + cas-server-extension-clearpass + + Apereo CAS ClearPass Extension - DEPRECATED + CAS ClearPass Extension + jar + + + ${project.parent.basedir} + + + + + + + + + + + org.jasig.cas + cas-server-core + ${project.version} + compile + + + + net.sf.ehcache + ehcache + compile + + + + org.jasig.cas + cas-server-integration-ehcache + ${project.version} + test + + + + org.jasig.cas + cas-server-core + ${project.version} + test-jar + test + + + + org.jasig.cas.client + cas-client-core + compile + + + diff --git a/cas-server-extension-clearpass/src/main/java/org/jasig/cas/extension/clearpass/CacheCredentialsMetaDataPopulator.java b/cas-server-extension-clearpass/src/main/java/org/jasig/cas/extension/clearpass/CacheCredentialsMetaDataPopulator.java new file mode 100644 index 000000000000..7ac21460ca85 --- /dev/null +++ b/cas-server-extension-clearpass/src/main/java/org/jasig/cas/extension/clearpass/CacheCredentialsMetaDataPopulator.java @@ -0,0 +1,65 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.extension.clearpass; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.AuthenticationBuilder; +import org.jasig.cas.authentication.AuthenticationMetaDataPopulator; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.UsernamePasswordCredential; + +import javax.validation.constraints.NotNull; +import java.util.Map; + +/** + * We cheat and utilize the {@link org.jasig.cas.authentication.AuthenticationMetaDataPopulator} to retrieve and store + * the password in our cache rather than use the original design which relied on modifying the flow. This method, while + * technically a misuse of the interface relieves us of having to modify and maintain the login flow manually. + * + * @deprecated As of 4.1, use {@link org.jasig.cas.authentication.CacheCredentialsMetaDataPopulator} instead. + * @author Scott Battaglia + * @since 1.0 + */ +@Deprecated +public final class CacheCredentialsMetaDataPopulator implements AuthenticationMetaDataPopulator { + + @NotNull + private final Map credentialCache; + + /** + * Instantiates a new cache credentials meta data populator. + * + * @param credentialCache the credential cache + */ + public CacheCredentialsMetaDataPopulator(final Map credentialCache) { + this.credentialCache = credentialCache; + } + + @Override + public void populateAttributes(final AuthenticationBuilder builder, final Credential credential) { + final UsernamePasswordCredential c = (UsernamePasswordCredential) credential; + final Authentication authentication = builder.build(); + this.credentialCache.put(authentication.getPrincipal().getId(), c.getPassword()); + } + + @Override + public boolean supports(final Credential credential) { + return credential instanceof UsernamePasswordCredential; + } +} diff --git a/cas-server-extension-clearpass/src/main/java/org/jasig/cas/extension/clearpass/ClearPassController.java b/cas-server-extension-clearpass/src/main/java/org/jasig/cas/extension/clearpass/ClearPassController.java new file mode 100644 index 000000000000..29537b90b4ca --- /dev/null +++ b/cas-server-extension-clearpass/src/main/java/org/jasig/cas/extension/clearpass/ClearPassController.java @@ -0,0 +1,118 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.extension.clearpass; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.AbstractController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; +import java.util.Map; + +/** + * A controller that returns the password based on some external authentication/authorization rules. The recommended + * method is to use the Apereo CAS Client for Java and its proxy authentication features. + * + * @deprecated As of 4.1, use {@link org.jasig.cas.authentication.CacheCredentialsMetaDataPopulator} instead. + * @author Scott Battaglia + * @since 1.0 + */ +@Deprecated +public final class ClearPassController extends AbstractController { + + /** view if clearpass request fails. */ + protected static final String DEFAULT_SERVICE_FAILURE_VIEW_NAME = "protocol/clearPass/clearPassFailure"; + + /** view if clearpass request succeeds. */ + protected static final String DEFAULT_SERVICE_SUCCESS_VIEW_NAME = "protocol/clearPass/clearPassSuccess"; + + /** key under which clearpass will be placed into the model. */ + protected static final String MODEL_CLEARPASS = "credentials"; + + /** key under which failure descriptions are placed into the model. */ + protected static final String MODEL_FAILURE_DESCRIPTION = "description"; + + private static final Logger LOGGER = LoggerFactory.getLogger(ClearPassController.class); + + @NotNull + private String successView = DEFAULT_SERVICE_SUCCESS_VIEW_NAME; + + @NotNull + private String failureView = DEFAULT_SERVICE_FAILURE_VIEW_NAME; + + @NotNull + private final Map credentialsCache; + + /** + * Instantiates a new clear pass controller. + * + * @param credentialsCache the credentials cache + */ + public ClearPassController(final Map credentialsCache) { + this.credentialsCache = credentialsCache; + } + + @Override + public ModelAndView handleRequestInternal(final HttpServletRequest request, + final HttpServletResponse response) throws Exception { + final String userName = request.getRemoteUser(); + + LOGGER.debug("Handling clearPass request for user [{}]", userName); + + if (StringUtils.isBlank(userName)) { + return returnError("No username was provided to clearPass."); + } + + if (!this.credentialsCache.containsKey(userName)) { + return returnError("Password could not be found in cache for user " + userName); + } + + final String password = this.credentialsCache.get(userName); + if (StringUtils.isBlank(password)) { + return returnError("Password is null or blank"); + } + + LOGGER.debug("Retrieved credentials will be provided to the requesting service."); + return new ModelAndView(this.successView, MODEL_CLEARPASS, password); + } + + /** + * Return error based on {@link #setFailureView(String)}. + * + * @param description the description + * @return the model and view + */ + protected ModelAndView returnError(final String description) { + final ModelAndView mv = new ModelAndView(this.failureView); + mv.addObject(MODEL_FAILURE_DESCRIPTION, description); + return mv; + } + + public void setSuccessView(final String successView) { + this.successView = successView; + } + + public void setFailureView(final String failureView) { + this.failureView = failureView; + } +} diff --git a/cas-server-extension-clearpass/src/main/java/org/jasig/cas/extension/clearpass/EhcacheBackedMap.java b/cas-server-extension-clearpass/src/main/java/org/jasig/cas/extension/clearpass/EhcacheBackedMap.java new file mode 100644 index 000000000000..3c2f2c619359 --- /dev/null +++ b/cas-server-extension-clearpass/src/main/java/org/jasig/cas/extension/clearpass/EhcacheBackedMap.java @@ -0,0 +1,170 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.extension.clearpass; + +import net.sf.ehcache.Cache; +import net.sf.ehcache.Element; + +import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * EhCache-backed implementation of a Map for caching a set of Strings. + * + * @deprecated As of 4.1, use {@link org.jasig.cas.authentication.CacheCredentialsMetaDataPopulator} instead. + * @author Scott Battaglia + * @since 1.0 + */ +@Deprecated +public final class EhcacheBackedMap implements Map { + + @NotNull + private final Cache cache; + + /** + * Instantiates a new ehcache backed map. + * + * @param cache the cache + */ + public EhcacheBackedMap(final Cache cache) { + this.cache = cache; + } + + @Override + public int size() { + return this.cache.getSize(); + } + + @Override + public boolean isEmpty() { + return this.cache.getSize() == 0; + } + + @Override + public boolean containsKey(final Object key) { + return get(key) != null; + } + + @Override + public boolean containsValue(final Object value) { + final Collection col = values(); + return col.contains(value); + } + + @Override + public String get(final Object key) { + final Element element = this.cache.get(key); + + return element == null ? null : (String) element.getValue(); + } + + @Override + public String put(final String key, final String value) { + this.cache.put(new Element(key, value)); + return value; + } + + @Override + public String remove(final Object key) { + final String keyValue = get(key); + this.cache.remove(key); + return keyValue; + } + + @Override + public void putAll(final Map m) { + for (final Map.Entry entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void clear() { + this.cache.removeAll(); + } + + @Override + public Set keySet() { + return new HashSet(this.cache.getKeys()); + } + + @Override + public Collection values() { + final Set keys = keySet(); + final Collection values = new ArrayList<>(); + + for (final String key : keys) { + final String value = get(key); + if (value != null) { + values.add(value); + } + } + + return values; + } + + @Override + public Set> entrySet() { + final Set keys = keySet(); + final Set> entries = new HashSet<>(); + + for (final String key : keys) { + final Element element = this.cache.get(key); + + if (element != null) { + entries.add(new ElementMapEntry(element)); + } + } + + return entries; + + } + + protected static final class ElementMapEntry implements Map.Entry { + + private final Element element; + + /** + * Instantiates a new element map entry. + * + * @param element the element + */ + public ElementMapEntry(final Element element) { + this.element = element; + } + @Override + public String getKey() { + return (String) element.getKey(); + } + + @Override + public String getValue() { + return (String) element.getValue(); + } + + @Override + public String setValue(final String value) { + throw new UnsupportedOperationException("Operation Not Supported"); + } + } +} diff --git a/cas-server-extension-clearpass/src/main/java/org/jasig/cas/extension/clearpass/EncryptedMapDecorator.java b/cas-server-extension-clearpass/src/main/java/org/jasig/cas/extension/clearpass/EncryptedMapDecorator.java new file mode 100644 index 000000000000..66b0a10722ef --- /dev/null +++ b/cas-server-extension-clearpass/src/main/java/org/jasig/cas/extension/clearpass/EncryptedMapDecorator.java @@ -0,0 +1,521 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.extension.clearpass; + +import org.jasig.cas.util.CompressionUtils; +import com.google.common.io.ByteSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import javax.validation.constraints.NotNull; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.security.Key; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.KeySpec; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * Decorator for a map that will hash the key and encrypt the value. + * + * @deprecated As of 4.1, use {@link org.jasig.cas.authentication.CacheCredentialsMetaDataPopulator} instead. + * @author Scott Battaglia + * @since 1.0.6 + */ +@Deprecated +public final class EncryptedMapDecorator implements Map { + + private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; + + private static final String SECRET_KEY_FACTORY_ALGORITHM = "PBKDF2WithHmacSHA1"; + + private static final String DEFAULT_HASH_ALGORITHM = "SHA-512"; + + private static final String DEFAULT_ENCRYPTION_ALGORITHM = "AES"; + + private static final int INTEGER_LEN = 4; + + private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + private static final int DEFAULT_SALT_SIZE = 8; + private static final int DEFAULT_SECRET_KEY_SIZE = 32; + private static final int BYTE_BUFFER_CAPACITY_SIZE = 4; + private static final int HEX_RIGHT_SHIFT_COEFFICIENT = 4; + private static final int HEX_HIGH_BITS_BITWISE_FLAG = 0x0f; + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @NotNull + private final Map decoratedMap; + + @NotNull + private final MessageDigest messageDigest; + + @NotNull + private final ByteSource salt; + + @NotNull + private final Key key; + + @NotNull + private int ivSize; + + @NotNull + private final String secretKeyAlgorithm; + + private boolean cloneNotSupported; + + /** + * Decorates a map using the default algorithm {@link #DEFAULT_HASH_ALGORITHM} and a + * {@link #DEFAULT_ENCRYPTION_ALGORITHM}. + *

The salt is randomly constructed when the object is created in memory. + * This constructor is sufficient to decorate + * a cache that only lives in-memory. + * + * @param decoratedMap the map to decorate. CANNOT be NULL. + * @throws Exception if the algorithm cannot be found. Should not happen in this case, or if the key spec is not found + * or if the key is invalid. Check the exception type for more details on the nature of the error. + */ + public EncryptedMapDecorator(final Map decoratedMap) throws Exception { + this(decoratedMap, getRandomSalt(DEFAULT_SALT_SIZE), getRandomSalt(DEFAULT_SECRET_KEY_SIZE)); + } + + /** + * Decorates a map using the default algorithm {@link #DEFAULT_HASH_ALGORITHM} + * and a {@link #DEFAULT_ENCRYPTION_ALGORITHM}. + *

Takes a salt and secretKey so that it can work with a distributed cache. + * + * @param decoratedMap the map to decorate. CANNOT be NULL. + * @param salt the salt, as a String. Gets converted to bytes. CANNOT be NULL. + * @param secretKey the secret to use for the key. Gets converted to bytes. CANNOT be NULL. + * @throws Exception if the algorithm cannot be found. Should not happen in this case, or if the key spec is not found + * or if the key is invalid. Check the exception type for more details on the nature of the error. + */ + public EncryptedMapDecorator(final Map decoratedMap, final String salt, + final String secretKey) throws Exception { + this(decoratedMap, DEFAULT_HASH_ALGORITHM, salt, DEFAULT_ENCRYPTION_ALGORITHM, secretKey); + } + + /** + * Decorates a map using the provided algorithms. + *

Takes a salt and secretKey so that it can work with a distributed cache. + * + * @param decoratedMap the map to decorate. CANNOT be NULL. + * @param hashAlgorithm the algorithm to use for hashing. CANNOT BE NULL. + * @param salt the salt, as a String. Gets converted to bytes. CANNOT be NULL. + * @param secretKeyAlgorithm the encryption algorithm. CANNOT BE NULL. + * @param secretKey the secret to use for the key. Gets converted to bytes. CANNOT be NULL. + * @throws Exception if the algorithm cannot be found. Should not happen in this case, or if the key spec is not found + * or if the key is invalid. Check the exception type for more details on the nature of the error. + */ + public EncryptedMapDecorator(final Map decoratedMap, final String hashAlgorithm, final String salt, + final String secretKeyAlgorithm, final String secretKey) throws Exception { + this(decoratedMap, hashAlgorithm, salt.getBytes(Charset.defaultCharset()), secretKeyAlgorithm, + getSecretKey(secretKeyAlgorithm, secretKey, salt)); + } + + /** + * Decorates a map using the provided algorithms. + *

Takes a salt and secretKey so that it can work with a distributed cache. + * + * @param decoratedMap the map to decorate. CANNOT be NULL. + * @param hashAlgorithm the algorithm to use for hashing. CANNOT BE NULL. + * @param salt the salt, as a String. Gets converted to bytes. CANNOT be NULL. + * @param secretKeyAlgorithm the encryption algorithm. CANNOT BE NULL. + * @param secretKey the secret to use. CANNOT be NULL. + * @throws RuntimeException if the algorithm cannot be found or the iv size cant be determined. + */ + public EncryptedMapDecorator(final Map decoratedMap, final String hashAlgorithm, final byte[] salt, + final String secretKeyAlgorithm, final Key secretKey) { + try { + this.decoratedMap = decoratedMap; + this.key = secretKey; + this.salt = ByteSource.wrap(salt); + this.secretKeyAlgorithm = secretKeyAlgorithm; + this.messageDigest = MessageDigest.getInstance(hashAlgorithm); + this.ivSize = getIvSize(); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Gets the random salt. + * + * @param size the size + * @return the random salt + */ + private static String getRandomSalt(final int size) { + final SecureRandom secureRandom = new SecureRandom(); + final byte[] bytes = new byte[size]; + + secureRandom.nextBytes(bytes); + + return getFormattedText(bytes); + } + + @Override + public int size() { + return this.decoratedMap.size(); + } + + @Override + public boolean isEmpty() { + return this.decoratedMap.isEmpty(); + } + + @Override + public boolean containsKey(final Object key) { + final String hashedKey = constructHashedKey(key.toString()); + return this.decoratedMap.containsKey(hashedKey); + } + + @Override + public boolean containsValue(final Object value) { + if (!(value instanceof String)) { + return false; + } + + final String encryptedValue = encrypt((String) value); + return this.decoratedMap.containsValue(encryptedValue); + } + + @Override + public String get(final Object key) { + final String hashedKey = constructHashedKey(key == null ? null : key.toString()); + return decrypt(this.decoratedMap.get(hashedKey), hashedKey); + } + + @Override + public String put(final String key, final String value) { + final String hashedKey = constructHashedKey(key); + final String hashedValue = encrypt(value, hashedKey); + final String oldValue = this.decoratedMap.put(hashedKey, hashedValue); + + return decrypt(oldValue, hashedKey); + } + + @Override + public String remove(final Object key) { + final String hashedKey = constructHashedKey(key.toString()); + return decrypt(this.decoratedMap.remove(hashedKey), hashedKey); + } + + @Override + public void putAll(final Map m) { + for (final Entry entry : m.entrySet()) { + this.put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void clear() { + this.decoratedMap.clear(); + } + + @Override + public Set keySet() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection values() { + throw new UnsupportedOperationException(); + } + + @Override + public Set> entrySet() { + throw new UnsupportedOperationException(); + } + + /** + * Construct hashed key. + * + * @param key the key + * @return the string + */ + protected String constructHashedKey(final String key) { + if (key == null) { + return null; + } + + final MessageDigest messageDigest = getMessageDigest(); + messageDigest.update(consumeByteSourceOrNull(this.salt)); + messageDigest.update(key.toLowerCase().getBytes(Charset.defaultCharset())); + final String hash = getFormattedText(messageDigest.digest()); + + logger.debug("Generated hash of value [{}] for key [{}].", hash, key); + return hash; + } + + /** + * Decrypt the value. + * + * @param value the value + * @param hashedKey the hashed key + * @return the string + */ + protected String decrypt(final String value, final String hashedKey) { + if (value == null) { + return null; + } + + try { + final Cipher cipher = getCipherObject(); + final byte[] ivCiphertext = CompressionUtils.decodeBase64ToByteArray(value); + final int ivSize = byte2int(Arrays.copyOfRange(ivCiphertext, 0, INTEGER_LEN)); + final byte[] ivValue = Arrays.copyOfRange(ivCiphertext, INTEGER_LEN, (INTEGER_LEN + ivSize)); + final byte[] ciphertext = Arrays.copyOfRange(ivCiphertext, INTEGER_LEN + ivSize, ivCiphertext.length); + final IvParameterSpec ivSpec = new IvParameterSpec(ivValue); + + cipher.init(Cipher.DECRYPT_MODE, this.key, ivSpec); + + final byte[] plaintext = cipher.doFinal(ciphertext); + + return new String(plaintext, Charset.defaultCharset()); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Read the contents of the source into a byte array. + * @param source the byte array source + * @return the byte[] read from the source or null + */ + private byte[] consumeByteSourceOrNull(final ByteSource source) { + try { + if (source == null || source.isEmpty()) { + return null; + } + return source.read(); + } catch (final IOException e) { + logger.warn("Could not consume the byte array source", e); + return null; + } + } + + /** + * Gets the iv size. + * + * @return the iv size + * @throws NoSuchAlgorithmException the no such algorithm exception + * @throws NoSuchPaddingException the no such padding exception + */ + private int getIvSize() throws NoSuchAlgorithmException, NoSuchPaddingException { + return getCipherObject().getBlockSize(); + } + + /** + * Generate iv. + * + * @param size the size + * @return the iv value + */ + private static byte[] generateIV(final int size) { + final SecureRandom srand = new SecureRandom(); + final byte[] ivValue = new byte[size]; + srand.nextBytes(ivValue); + return ivValue; + } + + + /** + * Encrypt. + * + * @param value the value + * @return the string + */ + protected String encrypt(final String value) { + return encrypt(value, null); + } + + /** + * Encrypt. + * + * @param value the value + * @param hashedKey the hashed key + * @return the string + */ + protected String encrypt(final String value, final String hashedKey) { + if (value == null) { + return null; + } + + try { + final Cipher cipher = getCipherObject(); + final byte[] ivValue = generateIV(this.ivSize); + final IvParameterSpec ivSpec = new IvParameterSpec(ivValue); + + cipher.init(Cipher.ENCRYPT_MODE, this.key, ivSpec); + + final byte[] ciphertext = cipher.doFinal(value.getBytes(Charset.defaultCharset())); + final byte[] ivCiphertext = new byte[INTEGER_LEN + this.ivSize + ciphertext.length]; + + System.arraycopy(int2byte(this.ivSize), 0, ivCiphertext, 0, INTEGER_LEN); + System.arraycopy(ivValue, 0, ivCiphertext, INTEGER_LEN, this.ivSize); + System.arraycopy(ciphertext, 0, ivCiphertext, INTEGER_LEN + this.ivSize, ciphertext.length); + + return CompressionUtils.encodeBase64(ivCiphertext); + } catch(final Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Int to byte. + * + * @param i the i + * @return the byte[] + * @throws UnsupportedEncodingException the unsupported encoding exception + */ + protected static byte[] int2byte(final int i) throws UnsupportedEncodingException { + return ByteBuffer.allocate(BYTE_BUFFER_CAPACITY_SIZE).putInt(i).array(); + } + + /** + * Byte to int. + * + * @param bytes the bytes + * @return the int + * @throws UnsupportedEncodingException the unsupported encoding exception + */ + protected static int byte2int(final byte[] bytes) throws UnsupportedEncodingException { + return ByteBuffer.wrap(bytes).getInt(); + } + + /** + * Byte to char. + * + * @param bytes the bytes + * @return the string + * @throws UnsupportedEncodingException the unsupported encoding exception + */ + protected static String byte2char(final byte[] bytes) throws UnsupportedEncodingException { + return new String(bytes, "UTF-8"); + } + + /** + * Char to byte. + * + * @param chars the chars + * @return the byte[] + * @throws UnsupportedEncodingException the unsupported encoding exception + */ + protected static byte[] char2byte(final String chars) throws UnsupportedEncodingException { + return chars.getBytes("UTF-8"); + } + + /** + * Tries to clone the {@link MessageDigest} that was created during construction. If the clone fails + * that is remembered and from that point on new {@link MessageDigest} instances will be created on + * every call. + *

+ * Adopted from the Spring EhCache Annotations project. + * + * @return Generates a {@link MessageDigest} to use + */ + protected MessageDigest getMessageDigest() { + if (this.cloneNotSupported) { + final String algorithm = this.messageDigest.getAlgorithm(); + try { + return MessageDigest.getInstance(algorithm); + } catch (final NoSuchAlgorithmException e) { + throw new IllegalStateException("MessageDigest algorithm '" + algorithm + "' was supported when " + + this.getClass().getSimpleName() + + " was created but is not now. This should not be possible.", e); + } + } + + try { + return (MessageDigest) this.messageDigest.clone(); + } catch (final CloneNotSupportedException e) { + this.cloneNotSupported = true; + final String msg = String.format("Could not clone MessageDigest using algorithm '%s'. " + + "MessageDigest.getInstance will be used from now on which will be much more expensive.", + this.messageDigest.getAlgorithm()); + logger.warn(msg, e); + return this.getMessageDigest(); + } + } + + /** + * Takes the raw bytes from the digest and formats them. + * + * @param bytes the raw bytes from the digest. + * @return the formatted bytes. + */ + private static String getFormattedText(final byte[] bytes) { + final StringBuilder buf = new StringBuilder(bytes.length * 2); + + for (final byte b : bytes) { + buf.append(HEX_DIGITS[b >> HEX_RIGHT_SHIFT_COEFFICIENT & HEX_HIGH_BITS_BITWISE_FLAG]); + buf.append(HEX_DIGITS[b & HEX_HIGH_BITS_BITWISE_FLAG]); + } + return buf.toString(); + } + + /** + * Gets the cipher object for the {@link #CIPHER_ALGORITHM}. + * + * @return the cipher object + * @throws NoSuchAlgorithmException - if transformation is null, empty, in an invalid format, or if no Provider + * supports a CipherSpi implementation for the specified algorithm. + * @throws NoSuchPaddingException - if transformation contains a padding scheme that is not available. + * @see Cipher#getInstance(String) + */ + private Cipher getCipherObject() throws NoSuchAlgorithmException, NoSuchPaddingException { + return Cipher.getInstance(CIPHER_ALGORITHM); + } + + /** + * Gets the secret key. + * + * @param secretKeyAlgorithm the secret key algorithm + * @param secretKey the secret key + * @param salt the salt + * @return the secret key + * @throws Exception the exception + */ + private static Key getSecretKey(final String secretKeyAlgorithm, final String secretKey, + final String salt) throws Exception { + + final SecretKeyFactory factory = SecretKeyFactory.getInstance(SECRET_KEY_FACTORY_ALGORITHM); + final KeySpec spec = new PBEKeySpec(secretKey.toCharArray(), char2byte(salt), 65536, 128); + final SecretKey tmp = factory.generateSecret(spec); + return new SecretKeySpec(tmp.getEncoded(), secretKeyAlgorithm); + } + + public String getSecretKeyAlgorithm() { + return secretKeyAlgorithm; + } +} diff --git a/cas-server-extension-clearpass/src/main/java/org/jasig/cas/extension/clearpass/TicketRegistryDecorator.java b/cas-server-extension-clearpass/src/main/java/org/jasig/cas/extension/clearpass/TicketRegistryDecorator.java new file mode 100644 index 000000000000..36a71975b1e7 --- /dev/null +++ b/cas-server-extension-clearpass/src/main/java/org/jasig/cas/extension/clearpass/TicketRegistryDecorator.java @@ -0,0 +1,118 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.extension.clearpass; + +import org.jasig.cas.monitor.TicketRegistryState; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.registry.AbstractTicketRegistry; +import org.jasig.cas.ticket.registry.TicketRegistry; + +import javax.validation.constraints.NotNull; +import java.util.Collection; +import java.util.Map; + +/** + * Decorator that captures tickets and attempts to map them. + * + * @deprecated As of 4.1, use {@link org.jasig.cas.authentication.CacheCredentialsMetaDataPopulator} instead. + * @author Scott Battaglia + * @since 1.0.7 + */ +@Deprecated +public final class TicketRegistryDecorator extends AbstractTicketRegistry { + + /** The real instance of the ticket registry that is to be decorated. */ + @NotNull + private final TicketRegistry ticketRegistry; + + /** Map instance where credentials are stored. */ + @NotNull + private final Map cache; + + /** + * Constructs an instance of the decorator wrapping the real ticket registry instance inside. + * + * @param actualTicketRegistry The real instance of the ticket registry that is to be decorated + * @param cache Map instance where credentials are stored. + * + * @see EhcacheBackedMap + */ + public TicketRegistryDecorator(final TicketRegistry actualTicketRegistry, final Map cache) { + this.ticketRegistry = actualTicketRegistry; + this.cache = cache; + } + + @Override + public void addTicket(final Ticket ticket) { + if (ticket instanceof TicketGrantingTicket) { + final TicketGrantingTicket ticketGrantingTicket = (TicketGrantingTicket) ticket; + final String ticketId = ticketGrantingTicket.getId(); + final String userName = ticketGrantingTicket.getAuthentication().getPrincipal().getId().toLowerCase(); + + logger.debug("Creating mapping ticket {} to user name {}", ticketId, userName); + + this.cache.put(ticketId, userName); + } + + this.ticketRegistry.addTicket(ticket); + } + + @Override + public Ticket getTicket(final String ticketId) { + return this.ticketRegistry.getTicket(ticketId); + } + + @Override + public boolean deleteTicket(final String ticketId) { + final String userName = this.cache.get(ticketId); + + if (userName != null) { + logger.debug("Removing mapping ticket {} for user name {}", ticketId, userName); + this.cache.remove(userName); + } + + return this.ticketRegistry.deleteTicket(ticketId); + } + + @Override + public Collection getTickets() { + return this.ticketRegistry.getTickets(); + } + + @Override + public int sessionCount() { + if (this.ticketRegistry instanceof TicketRegistryState) { + return ((TicketRegistryState) this.ticketRegistry).sessionCount(); + } + logger.debug("Ticket registry {} does not report the sessionCount() operation of the registry state.", + this.ticketRegistry.getClass().getName()); + return super.sessionCount(); + } + + @Override + public int serviceTicketCount() { + if (this.ticketRegistry instanceof TicketRegistryState) { + return ((TicketRegistryState) this.ticketRegistry).serviceTicketCount(); + } + logger.debug("Ticket registry {} does not report the serviceTicketCount() operation of the registry state.", + this.ticketRegistry.getClass().getName()); + return super.serviceTicketCount(); + } +} diff --git a/cas-server-extension-clearpass/src/main/resources/ehcacheClearPass.xml b/cas-server-extension-clearpass/src/main/resources/ehcacheClearPass.xml new file mode 100644 index 000000000000..45943eaea6b6 --- /dev/null +++ b/cas-server-extension-clearpass/src/main/resources/ehcacheClearPass.xml @@ -0,0 +1,28 @@ + + + + + + diff --git a/cas-server-extension-clearpass/src/site/site.xml b/cas-server-extension-clearpass/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-extension-clearpass/src/site/site.xml @@ -0,0 +1,28 @@ + + + + +

+ + + + diff --git a/cas-server-extension-clearpass/src/test/java/log4j2.xml b/cas-server-extension-clearpass/src/test/java/log4j2.xml new file mode 100644 index 000000000000..73249658e736 --- /dev/null +++ b/cas-server-extension-clearpass/src/test/java/log4j2.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + diff --git a/cas-server-extension-clearpass/src/test/java/org/jasig/cas/extension/clearpass/CacheCredentialsMetaDataPopulatorTests.java b/cas-server-extension-clearpass/src/test/java/org/jasig/cas/extension/clearpass/CacheCredentialsMetaDataPopulatorTests.java new file mode 100644 index 000000000000..c1ce77297fbc --- /dev/null +++ b/cas-server-extension-clearpass/src/test/java/org/jasig/cas/extension/clearpass/CacheCredentialsMetaDataPopulatorTests.java @@ -0,0 +1,76 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.extension.clearpass; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.DefaultAuthenticationBuilder; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Tests for {@link CacheCredentialsMetaDataPopulator}. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class CacheCredentialsMetaDataPopulatorTests { + + @Test + public void verifyAttributePopulationWithPassword() { + final Authentication auth = TestUtils.getAuthentication(); + final Map map = new HashMap<>(); + final CacheCredentialsMetaDataPopulator populator = new CacheCredentialsMetaDataPopulator(map); + + final UsernamePasswordCredential c = TestUtils.getCredentialsWithSameUsernameAndPassword(); + populator.populateAttributes(DefaultAuthenticationBuilder.newInstance(auth), c); + + assertTrue(map.containsKey(auth.getPrincipal().getId())); + assertEquals(map.get(auth.getPrincipal().getId()), c.getPassword()); + } + + @Test + public void verifyAttributePopulationWithPasswordWithDifferentCredentialsType() { + final Authentication auth = TestUtils.getAuthentication(); + final Map map = new HashMap<>(); + final CacheCredentialsMetaDataPopulator populator = new CacheCredentialsMetaDataPopulator(map); + + final Credential c = new Credential() { + @Override + public String getId() { + return "something"; + } + }; + + if (populator.supports(c)) { + populator.populateAttributes(DefaultAuthenticationBuilder.newInstance(auth), c); + } + + assertEquals(map.size(), 0); + + } + +} diff --git a/cas-server-extension-clearpass/src/test/java/org/jasig/cas/extension/clearpass/ClearPassControllerTests.java b/cas-server-extension-clearpass/src/test/java/org/jasig/cas/extension/clearpass/ClearPassControllerTests.java new file mode 100644 index 000000000000..86eec915d00e --- /dev/null +++ b/cas-server-extension-clearpass/src/test/java/org/jasig/cas/extension/clearpass/ClearPassControllerTests.java @@ -0,0 +1,91 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.extension.clearpass; + +import net.sf.ehcache.Cache; +import net.sf.ehcache.CacheManager; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.web.servlet.ModelAndView; + +import static org.junit.Assert.*; + +/** + * Tests for {@link org.jasig.cas.extension.clearpass.ClearPassController}. + * + * @author Misagh Moayyed mmoayyed@unicon.net + * @since 3.5.0 + */ +public class ClearPassControllerTests { + + private CacheManager manager; + private Cache cache; + private EhcacheBackedMap map; + + @Before + public void prep() { + this.manager = CacheManager.create(); + this.manager.addCache("cascache"); + this.cache = this.manager.getCache("cascache"); + this.map = new EhcacheBackedMap(this.cache); + } + + @After + public void shutdown() { + this.manager.shutdown(); + } + + @Test + public void verifyClearPassWithNoUsername() throws Exception { + final ClearPassController controller = new ClearPassController(this.map); + final ModelAndView mv = controller.handleRequestInternal(new MockHttpServletRequest(), + new MockHttpServletResponse()); + assertEquals(mv.getViewName(), ClearPassController.DEFAULT_SERVICE_FAILURE_VIEW_NAME); + assertTrue(mv.getModel().containsKey(ClearPassController.MODEL_FAILURE_DESCRIPTION)); + } + + @Test + public void verifyClearPassWithUsernameMissingInCache() throws Exception { + final ClearPassController controller = new ClearPassController(this.map); + final MockHttpServletRequest req = new MockHttpServletRequest(); + req.setRemoteUser("casuser"); + final ModelAndView mv = controller.handleRequestInternal(req, + new MockHttpServletResponse()); + assertEquals(mv.getViewName(), ClearPassController.DEFAULT_SERVICE_FAILURE_VIEW_NAME); + assertTrue(mv.getModel().containsKey(ClearPassController.MODEL_FAILURE_DESCRIPTION)); + } + + @Test + public void verifyClearPassSuccess() throws Exception { + final ClearPassController controller = new ClearPassController(this.map); + final MockHttpServletRequest req = new MockHttpServletRequest(); + req.setRemoteUser("casuser"); + this.map.put("casuser", "password"); + + final ModelAndView mv = controller.handleRequestInternal(req, + new MockHttpServletResponse()); + assertEquals(mv.getViewName(), ClearPassController.DEFAULT_SERVICE_SUCCESS_VIEW_NAME); + assertTrue(mv.getModel().containsKey(ClearPassController.MODEL_CLEARPASS)); + assertTrue(mv.getModel().containsValue("password")); + } +} diff --git a/cas-server-extension-clearpass/src/test/java/org/jasig/cas/extension/clearpass/EhcacheBackedMapTests.java b/cas-server-extension-clearpass/src/test/java/org/jasig/cas/extension/clearpass/EhcacheBackedMapTests.java new file mode 100644 index 000000000000..367fb5f6b525 --- /dev/null +++ b/cas-server-extension-clearpass/src/test/java/org/jasig/cas/extension/clearpass/EhcacheBackedMapTests.java @@ -0,0 +1,104 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.extension.clearpass; + +import net.sf.ehcache.Cache; +import net.sf.ehcache.CacheManager; +import org.apache.commons.lang3.ArrayUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Map; + +import static org.junit.Assert.*; + +/** + * This is tests for + * {@link org.jasig.cas.extension.clearpass.EhcacheBackedMap}. + * @author Misagh Moayyed mmoayyed@unicon.net + * @since 3.5.0 + */ +public class EhcacheBackedMapTests { + + private CacheManager manager; + private Cache cache; + private EhcacheBackedMap map; + + @Before + public void prep() { + this.manager = CacheManager.create(); + this.manager.addCache("cascache"); + this.cache = this.manager.getCache("cascache"); + this.map = new EhcacheBackedMap(this.cache); + } + + @After + public void shutdown() { + this.manager.shutdown(); + } + + @Test + public void verifyEmptyMapSize() { + assertEquals(map.size(), 0); + assertTrue(map.isEmpty()); + } + + @Test + public void verifyGetPutOps() { + this.map.put("key", "value"); + assertNotNull(this.map.get("key")); + + this.map.remove("key"); + assertTrue(map.isEmpty()); + } + + @Test + public void verifyPutClear() { + final String[][] arrayItems = {{"key0", "Item0"}, {"key1", "Item1"}, {"key2", "Item2"}}; + final Map mapItems = ArrayUtils.toMap(arrayItems); + + this.map.putAll(mapItems); + assertEquals(map.size(), 3); + + this.map.clear(); + assertTrue(map.isEmpty()); + } + + @Test + public void verifyKeysValues() { + final String[][] arrayItems = {{"key0", "Item0"}, {"key1", "Item1"}, {"key2", "Item2"}}; + final Map mapItems = ArrayUtils.toMap(arrayItems); + + this.map.putAll(mapItems); + assertEquals(map.keySet().size(), 3); + assertEquals(map.values().size(), 3); + } + + @Test + public void verifyContains() { + final String[][] arrayItems = {{"key0", "Item0"}, {"key1", "Item1"}, {"key2", "Item2"}}; + final Map mapItems = ArrayUtils.toMap(arrayItems); + + this.map.putAll(mapItems); + assertTrue(map.containsKey("key2")); + assertTrue(map.containsValue("Item1")); + } +} diff --git a/cas-server-extension-clearpass/src/test/java/org/jasig/cas/extension/clearpass/EncryptedMapDecoratorTests.java b/cas-server-extension-clearpass/src/test/java/org/jasig/cas/extension/clearpass/EncryptedMapDecoratorTests.java new file mode 100644 index 000000000000..a74142ccd0ae --- /dev/null +++ b/cas-server-extension-clearpass/src/test/java/org/jasig/cas/extension/clearpass/EncryptedMapDecoratorTests.java @@ -0,0 +1,122 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.extension.clearpass; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import java.util.Map; + +import net.sf.ehcache.Cache; +import net.sf.ehcache.CacheManager; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Scott Battaglia + * @since 1.0.6 + */ +public class EncryptedMapDecoratorTests { + + private Map map; + + private EncryptedMapDecorator decorator; + + private CacheManager cacheManager; + + @Before + public void setUp() throws Exception { + try { + this.cacheManager = new CacheManager(this.getClass().getClassLoader() + .getResourceAsStream("ehcacheClearPass.xml")); + final Cache cache = this.cacheManager.getCache("clearPassCache"); + this.map = new EhcacheBackedMap(cache); + this.decorator = new EncryptedMapDecorator(map); + } catch (final Exception e) { + fail(e.getMessage()); + } + } + + @After + public void tearDown() throws Exception { + this.cacheManager.removalAll(); + this.cacheManager.shutdown(); + } + + @Test + public void addItem() { + final String key = "MY_KEY"; + final String value = "MY_VALUE"; + this.decorator.put(key, value); + assertEquals(value, this.decorator.get(key)); + assertNull(this.map.get(key)); + } + + @Test + public void addManyItems() { + final int totalItems = 100; + + for (int i = 0; i < totalItems; i++) { + this.decorator.put("key" + i, "value" + i); + } + + assertEquals(this.decorator.size(), totalItems); + + for (int i = 0; i < totalItems; i++) { + assertNull(this.map.get("key" + i)); + assertEquals("value" + i, this.decorator.get("key" + i)); + } + } + + @Test + public void addAndRemoveItem() { + final String key1 = "MY_REALLY_KEY"; + final String value1 = "MY_VALUE"; + final String key2 = "MY_KEY2"; + final String value2 = "MY_VALUE2"; + + this.decorator.put(key1, value1); + this.decorator.put(key2, value2); + assertEquals(value1, this.decorator.get(key1)); + assertEquals(value2, this.decorator.get(key2)); + assertNull(this.map.get(key1)); + assertNull(this.map.get(key2)); + + assertEquals(value1, this.decorator.remove(key1)); + assertEquals(value2, this.decorator.remove(key2)); + + assertNull(this.decorator.get(key1)); + assertNull(this.decorator.get(key2)); + } + + @Test + public void addNullKeyAndValue() { + this.decorator.put(null, null); + assertNull(this.decorator.get(null)); + } + + @Test + public void addNullValue() { + this.decorator.put("hello", null); + assertNull(this.decorator.get("hello")); + } +} diff --git a/cas-server-extension-clearpass/src/test/java/org/jasig/cas/extension/clearpass/TicketRegistryDecoratorTests.java b/cas-server-extension-clearpass/src/test/java/org/jasig/cas/extension/clearpass/TicketRegistryDecoratorTests.java new file mode 100644 index 000000000000..cdcfd75f74b2 --- /dev/null +++ b/cas-server-extension-clearpass/src/test/java/org/jasig/cas/extension/clearpass/TicketRegistryDecoratorTests.java @@ -0,0 +1,73 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.extension.clearpass; + +import java.util.HashMap; +import java.util.Map; + +import net.sf.ehcache.Cache; +import net.sf.ehcache.CacheManager; + +import org.jasig.cas.ticket.registry.DefaultTicketRegistry; +import org.jasig.cas.ticket.registry.EhCacheTicketRegistry; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Misagh Moayyed + * @since 3.5.0 + */ +public class TicketRegistryDecoratorTests { + + @Test + public void verifyDefaultTicketRegistryWithClearPass() { + + final TicketRegistry ticketRegistry = new DefaultTicketRegistry(); + final Map map = new HashMap<>(); + final TicketRegistryDecorator decorator = new TicketRegistryDecorator(ticketRegistry, map); + assertNotNull(decorator); + assertEquals(decorator.serviceTicketCount(), 0); + assertEquals(decorator.sessionCount(), 0); + } + + @Test + public void verifyEhCacheTicketRegistryWithClearPass() { + final Cache serviceTicketsCache = new Cache("serviceTicketsCache", 200, false, false, 100, 100); + final Cache ticketGrantingTicketCache = new Cache("ticketGrantingTicketCache", 200, false, false, 100, 100); + + final CacheManager manager = new CacheManager(this.getClass().getClassLoader().getResourceAsStream("ehcacheClearPass.xml")); + manager.addCache(serviceTicketsCache); + manager.addCache(ticketGrantingTicketCache); + + final Map map = new HashMap<>(); + + final TicketRegistry ticketRegistry = new EhCacheTicketRegistry(serviceTicketsCache, ticketGrantingTicketCache); + final TicketRegistryDecorator decorator = new TicketRegistryDecorator(ticketRegistry, map); + assertNotNull(decorator); + + assertEquals(decorator.serviceTicketCount(), 0); + assertEquals(decorator.sessionCount(), 0); + + manager.removalAll(); + manager.shutdown(); + + } +} diff --git a/cas-server-extension-clearpass/src/test/resources/ehcacheClearPass.xml b/cas-server-extension-clearpass/src/test/resources/ehcacheClearPass.xml new file mode 100644 index 000000000000..10cffb3f3d1e --- /dev/null +++ b/cas-server-extension-clearpass/src/test/resources/ehcacheClearPass.xml @@ -0,0 +1,28 @@ + + + + + + diff --git a/cas-server-integration-ehcache/NOTICE b/cas-server-integration-ehcache/NOTICE new file mode 100644 index 000000000000..82d2ec5f93af --- /dev/null +++ b/cas-server-integration-ehcache/NOTICE @@ -0,0 +1,103 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS Ehcache Integration under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + CDI APIs under Apache License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + ehcache under The Apache Software License, Version 2.0 + Ehcache JCache Implementation under The Apache Software License, Version 2.0 + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-integration-ehcache/pom.xml b/cas-server-integration-ehcache/pom.xml new file mode 100644 index 000000000000..e6c387b1fff4 --- /dev/null +++ b/cas-server-integration-ehcache/pom.xml @@ -0,0 +1,112 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-integration-ehcache + jar + Apereo CAS Ehcache Integration + + + + net.sf.ehcache + ehcache + + + org.jasig.cas + cas-server-core + ${project.version} + + + org.ehcache + jcache + 1.0.0 + + + org.slf4j + slf4j-api + + + + + org.jasig.cas + cas-server-core + ${project.version} + test-jar + test + + + + + + wgthom + Bill Thompson + wgthom@unicon.net + https://github.com/wgthom + Unicon, Inc. + http://www.unicon.net/ + + developer + maintainer + + + + + + Adam Rybicki + arybicki@unicon.net + http://www.unicon.net + Unicon, Inc. + http://www.unicon.net/ + + developer + + + + Andrew Tillinghast + atilling@conncoll.edu + https://github.com/atilling + Connecticut College + http://conncoll.edu/ + + developer + maintainer + + + + Andrew Petro + apetro@unicon.net + https://github.com/apetro + Unicon, Inc. + http://www.unicon.net + + maintainer + + + + + + ${project.parent.basedir} + + diff --git a/cas-server-integration-ehcache/src/main/java/org/jasig/cas/monitor/EhCacheMonitor.java b/cas-server-integration-ehcache/src/main/java/org/jasig/cas/monitor/EhCacheMonitor.java new file mode 100644 index 000000000000..c3e9efd698b6 --- /dev/null +++ b/cas-server-integration-ehcache/src/main/java/org/jasig/cas/monitor/EhCacheMonitor.java @@ -0,0 +1,52 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import javax.validation.constraints.NotNull; + +import net.sf.ehcache.Cache; + +/** + * Monitors a {@link net.sf.ehcache.Cache} instance. + * The accuracy of statistics is governed by the value of {@link Cache#getStatistics()}. + * + *

NOTE: computation of highly accurate statistics is expensive.

+ * + * @author Marvin S. Addison + * @since 3.5.1 + */ +public class EhCacheMonitor extends AbstractCacheMonitor { + + @NotNull + private final Cache cache; + + /** + * Instantiates a new EhCache monitor. + * + * @param cache the cache + */ + public EhCacheMonitor(final Cache cache) { + this.cache = cache; + } + + @Override + protected CacheStatistics[] getStatistics() { + return new EhCacheStatistics[] {new EhCacheStatistics(cache)}; + } +} diff --git a/cas-server-integration-ehcache/src/main/java/org/jasig/cas/monitor/EhCacheStatistics.java b/cas-server-integration-ehcache/src/main/java/org/jasig/cas/monitor/EhCacheStatistics.java new file mode 100644 index 000000000000..525a755777e2 --- /dev/null +++ b/cas-server-integration-ehcache/src/main/java/org/jasig/cas/monitor/EhCacheStatistics.java @@ -0,0 +1,138 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import net.sf.ehcache.Cache; +import net.sf.ehcache.config.CacheConfiguration; +import net.sf.ehcache.statistics.StatisticsGateway; +import org.apache.commons.lang3.StringUtils; +import java.util.Formatter; + +/** + * Ehcache statistics wrapper. + * + * @author Marvin S. Addison + * @since 3.5.1 + */ +public class EhCacheStatistics implements CacheStatistics { + + private static final double TOTAL_NUMBER_BYTES_IN_ONE_MEGABYTE = 1048510.0; + private static final int PERCENTAGE_VALUE = 100; + private final Cache cache; + + // Flag to determine whether size units are in bytes or simple object counts + private boolean useBytes; + + private long diskSize; + + private long heapSize; + + // Off heap size is always in units of bytes + private long offHeapSize; + + /** + * Creates a new instance that delegates statistics inquiries to the given {@link Cache} instance. + * + * @param cache Cache instance for which to gather statistics. + */ + public EhCacheStatistics(final Cache cache) { + this.cache = cache; + if (cache.getCacheConfiguration().getMaxBytesLocalDisk() > 0) { + useBytes = true; + } + } + + /** + * Gets the size of heap consumed by items stored in the cache. + * + * @return Memory size. + */ + @Override + public long getSize() { + final StatisticsGateway statistics = cache.getStatistics(); + // Store component sizes on each call to avoid recalculating + // sizes in other methods that need them + if (useBytes) { + diskSize = statistics.getLocalDiskSizeInBytes(); + heapSize = statistics.getLocalHeapSizeInBytes(); + } else { + diskSize = cache.getDiskStoreSize(); + heapSize = cache.getMemoryStoreSize(); + } + offHeapSize = statistics.getLocalOffHeapSizeInBytes(); + return heapSize; + } + + /** + * Gets the heap memory capacity of the cache. + * + * @return Heap memory capacity. + */ + @Override + public long getCapacity() { + final CacheConfiguration config = cache.getCacheConfiguration(); + if (useBytes) { + return config.getMaxBytesLocalDisk(); + } + return config.getMaxElementsOnDisk(); + } + + @Override + public long getEvictions() { + return cache.getStatistics().cacheEvictedCount(); + } + + @Override + public int getPercentFree() { + final long capacity = getCapacity(); + if (capacity == 0) { + return 0; + } + return (int) ((capacity - getSize()) * PERCENTAGE_VALUE / capacity); + } + + @Override + public String getName() { + return cache.getName(); + } + + @Override + public void toString(final StringBuilder builder) { + final String name = this.getName(); + if (StringUtils.isNotBlank(name)) { + builder.append(name).append(':'); + } + final int free = getPercentFree(); + final Formatter formatter = new Formatter(builder); + if (useBytes) { + formatter.format("%.2f", heapSize / TOTAL_NUMBER_BYTES_IN_ONE_MEGABYTE); + builder.append("MB heap, "); + formatter.format("%.2f", diskSize / TOTAL_NUMBER_BYTES_IN_ONE_MEGABYTE); + builder.append("MB disk, "); + } else { + builder.append(heapSize).append(" items in heap, "); + builder.append(diskSize).append(" items on disk, "); + } + formatter.format("%.2f", offHeapSize / TOTAL_NUMBER_BYTES_IN_ONE_MEGABYTE); + builder.append("MB off-heap, "); + builder.append(free).append("% free, "); + builder.append(getEvictions()).append(" evictions"); + formatter.close(); + } +} diff --git a/cas-server-integration-ehcache/src/main/java/org/jasig/cas/ticket/registry/EhCacheTicketRegistry.java b/cas-server-integration-ehcache/src/main/java/org/jasig/cas/ticket/registry/EhCacheTicketRegistry.java new file mode 100644 index 000000000000..0d7b32980821 --- /dev/null +++ b/cas-server-integration-ehcache/src/main/java/org/jasig/cas/ticket/registry/EhCacheTicketRegistry.java @@ -0,0 +1,242 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import net.sf.ehcache.Cache; +import net.sf.ehcache.Element; +import net.sf.ehcache.config.CacheConfiguration; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.springframework.beans.BeanInstantiationException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.style.ToStringCreator; + +import java.util.Collection; +import java.util.HashSet; + +/** + *

+ * Ehcache based distributed ticket registry. + *

+ * + *

+ * Use distinct caches for ticket granting tickets (TGT) and service tickets (ST) for: + *

    + *
  • Tuning : use cache level time to live with different values for TGT an ST.
  • + *
  • Monitoring : follow separately the number of TGT and ST.
  • + *
+ * + * @author Cyrille Le Clerc + * @author Adam Rybicki + * @author Andrew Tillinghast + * @since 3.5 + */ +public final class EhCacheTicketRegistry extends AbstractDistributedTicketRegistry implements InitializingBean { + + private Cache serviceTicketsCache; + + private Cache ticketGrantingTicketsCache; + + /** + * @see #setSupportRegistryState(boolean) + **/ + private boolean supportRegistryState = true; + + /** + * Instantiates a new EhCache ticket registry. + */ + public EhCacheTicketRegistry() { + + } + + /** + * Instantiates a new EhCache ticket registry. + * + * @param serviceTicketsCache the service tickets cache + * @param ticketGrantingTicketsCache the ticket granting tickets cache + */ + public EhCacheTicketRegistry(final Cache serviceTicketsCache, final Cache ticketGrantingTicketsCache) { + super(); + setServiceTicketsCache(serviceTicketsCache); + setTicketGrantingTicketsCache(ticketGrantingTicketsCache); + } + + /** + * Instantiates a new EhCache ticket registry. + * + * @param serviceTicketsCache the service tickets cache + * @param ticketGrantingTicketsCache the ticket granting tickets cache + * @param supportRegistryState the support registry state + */ + public EhCacheTicketRegistry(final Cache serviceTicketsCache, final Cache ticketGrantingTicketsCache, + final boolean supportRegistryState) { + this(serviceTicketsCache, ticketGrantingTicketsCache); + setSupportRegistryState(supportRegistryState); + } + + @Override + public void addTicket(final Ticket ticket) { + final Element element = new Element(ticket.getId(), ticket); + if (ticket instanceof ServiceTicket) { + logger.debug("Adding service ticket {} to the cache {}", ticket.getId(), this.serviceTicketsCache.getName()); + this.serviceTicketsCache.put(element); + } else if (ticket instanceof TicketGrantingTicket) { + logger.debug("Adding ticket granting ticket {} to the cache {}", ticket.getId(), + this.ticketGrantingTicketsCache.getName()); + this.ticketGrantingTicketsCache.put(element); + } else { + throw new IllegalArgumentException("Invalid ticket type " + ticket); + } + } + + @Override + public boolean deleteTicket(final String ticketId) { + if (StringUtils.isBlank(ticketId)) { + return false; + } + return this.serviceTicketsCache.remove(ticketId) || this.ticketGrantingTicketsCache.remove(ticketId); + } + + @Override + public Ticket getTicket(final String ticketId) { + if (ticketId == null) { + return null; + } + + Element element = this.serviceTicketsCache.get(ticketId); + if (element == null) { + element = this.ticketGrantingTicketsCache.get(ticketId); + } + return element == null ? null : getProxiedTicketInstance((Ticket) element.getObjectValue()); + } + + @Override + public Collection getTickets() { + final Collection serviceTickets = this.serviceTicketsCache.getAll( + this.serviceTicketsCache.getKeysWithExpiryCheck()).values(); + final Collection tgtTicketsTickets = this.ticketGrantingTicketsCache.getAll( + this.ticketGrantingTicketsCache.getKeysWithExpiryCheck()).values(); + + final Collection allTickets = new HashSet<>(serviceTickets.size() + tgtTicketsTickets.size()); + + for (final Element ticket : serviceTickets) { + allTickets.add((Ticket) ticket.getObjectValue()); + } + + for (final Element ticket : tgtTicketsTickets) { + allTickets.add((Ticket) ticket.getObjectValue()); + } + + return allTickets; + } + + public void setServiceTicketsCache(final Cache serviceTicketsCache) { + this.serviceTicketsCache = serviceTicketsCache; + } + + public void setTicketGrantingTicketsCache(final Cache ticketGrantingTicketsCache) { + this.ticketGrantingTicketsCache = ticketGrantingTicketsCache; + } + + @Override + public String toString() { + return new ToStringCreator(this).append("ticketGrantingTicketsCache", this.ticketGrantingTicketsCache) + .append("serviceTicketsCache", this.serviceTicketsCache).toString(); + } + + @Override + protected void updateTicket(final Ticket ticket) { + addTicket(ticket); + } + + @Override + protected boolean needsCallback() { + return false; + } + + /** + * Flag to indicate whether this registry instance should participate in reporting its state with + * default value set to true. + * Based on the EhCache documentation, + * determining the number of service tickets and the total session count from the cache can be considered + * an expensive operation with the time taken as O(n), where n is the number of elements in the cache. + * + *

Therefore, the flag provides a level of flexibility such that depending on the cache and environment + * settings, reporting statistics + * can be set to false and disabled.

+ * + * @param supportRegistryState true, if the registry is to support registry state + * @see #sessionCount() + * @see #serviceTicketCount() + * @see org.jasig.cas.monitor.SessionMonitor + */ + public void setSupportRegistryState(final boolean supportRegistryState) { + this.supportRegistryState = supportRegistryState; + } + + @Override + public void afterPropertiesSet() throws Exception { + if (this.serviceTicketsCache == null || this.ticketGrantingTicketsCache == null) { + throw new BeanInstantiationException(this.getClass(), + "Both serviceTicketsCache and ticketGrantingTicketsCache are required properties."); + } + + if (logger.isDebugEnabled()) { + CacheConfiguration config = this.serviceTicketsCache.getCacheConfiguration(); + logger.debug("serviceTicketsCache.maxElementsInMemory={}", config.getMaxEntriesLocalHeap()); + logger.debug("serviceTicketsCache.maxElementsOnDisk={}", config.getMaxElementsOnDisk()); + logger.debug("serviceTicketsCache.isOverflowToDisk={}", config.isOverflowToDisk()); + logger.debug("serviceTicketsCache.timeToLive={}", config.getTimeToLiveSeconds()); + logger.debug("serviceTicketsCache.timeToIdle={}", config.getTimeToIdleSeconds()); + logger.debug("serviceTicketsCache.cacheManager={}", this.serviceTicketsCache.getCacheManager().getName()); + + config = this.ticketGrantingTicketsCache.getCacheConfiguration(); + logger.debug("ticketGrantingTicketsCache.maxElementsInMemory={}", config.getMaxEntriesLocalHeap()); + logger.debug("ticketGrantingTicketsCache.maxElementsOnDisk={}", config.getMaxElementsOnDisk()); + logger.debug("ticketGrantingTicketsCache.isOverflowToDisk={}", config.isOverflowToDisk()); + logger.debug("ticketGrantingTicketsCache.timeToLive={}", config.getTimeToLiveSeconds()); + logger.debug("ticketGrantingTicketsCache.timeToIdle={}", config.getTimeToIdleSeconds()); + logger.debug("ticketGrantingTicketsCache.cacheManager={}", this.ticketGrantingTicketsCache.getCacheManager() + .getName()); + } + } + + /** + * {@inheritDoc} + * @see Cache#getKeysWithExpiryCheck() + */ + @Override + public int sessionCount() { + return BooleanUtils.toInteger(this.supportRegistryState, this.ticketGrantingTicketsCache + .getKeysWithExpiryCheck().size(), super.sessionCount()); + } + + /** + * {@inheritDoc} + * @see Cache#getKeysWithExpiryCheck() + */ + @Override + public int serviceTicketCount() { + return BooleanUtils.toInteger(this.supportRegistryState, this.serviceTicketsCache.getKeysWithExpiryCheck() + .size(), super.serviceTicketCount()); + } +} diff --git a/cas-server-integration-ehcache/src/site/site.xml b/cas-server-integration-ehcache/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-integration-ehcache/src/site/site.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/cas-server-integration-ehcache/src/test/java/org/jasig/cas/monitor/EhCacheMonitorTests.java b/cas-server-integration-ehcache/src/test/java/org/jasig/cas/monitor/EhCacheMonitorTests.java new file mode 100644 index 000000000000..91838e3e678d --- /dev/null +++ b/cas-server-integration-ehcache/src/test/java/org/jasig/cas/monitor/EhCacheMonitorTests.java @@ -0,0 +1,75 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import net.sf.ehcache.Cache; +import net.sf.ehcache.Element; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.assertEquals; + +/** + * Unit test for {@link EhCacheMonitor} class. + * + * @author Marvin S. Addison + * @since 3.5.1 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("/ehcacheMonitor-test.xml") +public class EhCacheMonitorTests { + + @Autowired + private Cache cache; + + @Autowired + private EhCacheMonitor monitor; + + @Test + public void verifyObserve() throws Exception { + CacheStatus status = monitor.observe(); + CacheStatistics stats = status.getStatistics()[0]; + assertEquals(100, stats.getCapacity()); + assertEquals(0, stats.getSize()); + assertEquals(StatusCode.OK, status.getCode()); + + // Fill cache 95% full, which is above 10% free WARN threshold + for (int i = 0; i < 95; i++) { + cache.put(new Element("key" + i, "value" + i)); + } + status = monitor.observe(); + stats = status.getStatistics()[0]; + assertEquals(100, stats.getCapacity()); + assertEquals(95, stats.getSize()); + assertEquals(StatusCode.WARN, status.getCode()); + + // Exceed the capacity and force evictions which should report WARN status + for (int i = 95; i < 110; i++) { + cache.put(new Element("key" + i, "value" + i)); + } + status = monitor.observe(); + stats = status.getStatistics()[0]; + assertEquals(100, stats.getCapacity()); + assertEquals(100, stats.getSize()); + assertEquals(StatusCode.WARN, status.getCode()); + } +} diff --git a/cas-server-integration-ehcache/src/test/java/org/jasig/cas/ticket/registry/EhCacheTicketRegistryTests.java b/cas-server-integration-ehcache/src/test/java/org/jasig/cas/ticket/registry/EhCacheTicketRegistryTests.java new file mode 100644 index 000000000000..ab715a28a794 --- /dev/null +++ b/cas-server-integration-ehcache/src/test/java/org/jasig/cas/ticket/registry/EhCacheTicketRegistryTests.java @@ -0,0 +1,263 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.*; + +/** + * Unit test for {@link EhCacheTicketRegistry}. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = "classpath:ticketRegistry.xml") +public final class EhCacheTicketRegistryTests implements ApplicationContextAware { + + private static final int TICKETS_IN_REGISTRY = 10; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private ApplicationContext applicationContext; + private TicketRegistry ticketRegistry; + + @Before + public void setUp() throws Exception { + this.ticketRegistry = this.applicationContext.getBean("ticketRegistry", TicketRegistry.class); + initTicketRegistry(); + } + + public static Service getService() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", "test"); + return SimpleWebApplicationServiceImpl.createServiceFrom(request); + } + + /** + * Method to add a TicketGrantingTicket to the ticket cache. This should add + * the ticket and return. Failure upon any exception. + */ + @Test + public void verifyAddTicketToCache() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + } catch (final Exception e) { + logger.error(e.getMessage(), e); + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyGetNullTicket() { + try { + this.ticketRegistry.getTicket(null, TicketGrantingTicket.class); + } catch (final Exception e) { + logger.error(e.getMessage(), e); + fail("Exception caught. None expected."); + } + } + + @Test + public void verifyGetNonExistingTicket() { + try { + this.ticketRegistry.getTicket("FALALALALALAL", TicketGrantingTicket.class); + } catch (final Exception e) { + logger.error(e.getMessage(), e); + fail("Exception caught. None expected."); + } + } + + @Test + public void verifyGetExistingTicketWithProperClass() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + this.ticketRegistry.getTicket("TEST", TicketGrantingTicket.class); + } catch (final Exception e) { + logger.error(e.getMessage(), e); + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyGetExistingTicketWithInproperClass() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + this.ticketRegistry.getTicket("TEST", ServiceTicket.class); + } catch (final ClassCastException e) { + return; + } + fail("ClassCastException expected."); + } + + @Test + public void verifyGetNullTicketWithoutClass() { + try { + this.ticketRegistry.getTicket(null); + } catch (final Exception e) { + logger.error(e.getMessage(), e); + fail("Exception caught. None expected."); + } + } + + @Test + public void verifyGetNonExistingTicketWithoutClass() { + try { + this.ticketRegistry.getTicket("FALALALALALAL"); + } catch (final Exception e) { + logger.error(e.getMessage(), e); + fail("Exception caught. None expected."); + } + } + + @Test + public void verifyGetExistingTicket() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + this.ticketRegistry.getTicket("TEST"); + } catch (final Exception e) { + logger.error(e.getMessage(), e); + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyDeleteExistingTicket() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + assertTrue("Ticket was not deleted.", this.ticketRegistry.deleteTicket("TEST")); + } catch (final Exception e) { + logger.error(e.getMessage(), e); + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyDeleteNonExistingTicket() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + assertFalse("Ticket was deleted.", this.ticketRegistry.deleteTicket("TEST1")); + } catch (final Exception e) { + logger.error(e.getMessage(), e); + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyDeleteNullTicket() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + assertFalse("Ticket was deleted.", this.ticketRegistry.deleteTicket(null)); + } catch (final Exception e) { + logger.error(e.getMessage(), e); + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyGetTicketsIsZero() { + try { + final int size = this.ticketRegistry.getTickets().size(); + assertEquals("The size of the empty registry is not zero.", size, 0); + } catch (final Exception e) { + logger.error(e.getMessage(), e); + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyGetTicketsFromRegistryEqualToTicketsAdded() { + final Collection tickets = new ArrayList<>(); + + for (int i = 0; i < TICKETS_IN_REGISTRY; i++) { + final TicketGrantingTicket ticketGrantingTicket = new TicketGrantingTicketImpl("TEST" + i, + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + final ServiceTicket st = ticketGrantingTicket.grantServiceTicket("tests" + i, getService(), + new NeverExpiresExpirationPolicy(), false); + tickets.add(ticketGrantingTicket); + tickets.add(st); + this.ticketRegistry.addTicket(ticketGrantingTicket); + this.ticketRegistry.addTicket(st); + } + + try { + final Collection ticketRegistryTickets = this.ticketRegistry.getTickets(); + assertEquals("The size of the registry is not the same as the collection.", ticketRegistryTickets.size(), + tickets.size()); + + for (final Ticket ticket : tickets) { + if (!ticketRegistryTickets.contains(ticket)) { + fail("Ticket was added to registry but was not found in retrieval of collection of all tickets."); + } + } + } catch (final Exception e) { + logger.error(e.getMessage(), e); + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Override + public void setApplicationContext(final ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + /** + * Cleaning ticket registry to start afresh, after newing up the instance. + * Leftover items from the cache interfere with the correctness of tests. + * Resetting the registry instance back to its default empty state allows each + * test to run an isolated mode independent of the previous state of either cache. + */ + private void initTicketRegistry() { + final Iterator it = this.ticketRegistry.getTickets().iterator(); + + while (it.hasNext()) { + this.ticketRegistry.deleteTicket(it.next().getId()); + } + } +} diff --git a/cas-server-integration-ehcache/src/test/resources/ehcache-failsafe.xml b/cas-server-integration-ehcache/src/test/resources/ehcache-failsafe.xml new file mode 100644 index 000000000000..dd6970fc944d --- /dev/null +++ b/cas-server-integration-ehcache/src/test/resources/ehcache-failsafe.xml @@ -0,0 +1,38 @@ + + + + + + + diff --git a/cas-server-integration-ehcache/src/test/resources/ehcache-replicated.xml b/cas-server-integration-ehcache/src/test/resources/ehcache-replicated.xml new file mode 100644 index 000000000000..8027b432ee6c --- /dev/null +++ b/cas-server-integration-ehcache/src/test/resources/ehcache-replicated.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + diff --git a/cas-server-integration-ehcache/src/test/resources/ehcacheMonitor-test.xml b/cas-server-integration-ehcache/src/test/resources/ehcacheMonitor-test.xml new file mode 100644 index 000000000000..c25aa5f92064 --- /dev/null +++ b/cas-server-integration-ehcache/src/test/resources/ehcacheMonitor-test.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-integration-ehcache/src/test/resources/log4j2.xml b/cas-server-integration-ehcache/src/test/resources/log4j2.xml new file mode 100644 index 000000000000..6dce5d0a80a2 --- /dev/null +++ b/cas-server-integration-ehcache/src/test/resources/log4j2.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-integration-ehcache/src/test/resources/ticketRegistry.xml b/cas-server-integration-ehcache/src/test/resources/ticketRegistry.xml new file mode 100644 index 000000000000..b78c59e2aa21 --- /dev/null +++ b/cas-server-integration-ehcache/src/test/resources/ticketRegistry.xml @@ -0,0 +1,133 @@ + + + + + Configuration for the EhCache TicketRegistry which stores the tickets in a distributed EhCache and cleans + them out as specified intervals. + + + + + + + + + + + + + + + + + + + + + + + + + + + Service Tickets (ST) and Proxy Tickets are only valid for short amount of time (default is 10 seconds), + and + most often are removed from the cache when the ST is validated. The ST cache must be replicated + quickly + since validation is expected within a few second after its creation. The CAS instance validating the + ST may + not be one that created the ST, since validation is a back-channel service-to-CAS call that is not + aware of + user session affinity. Synchronous mode is used to ensure all CAS nodes can validate the ST. + + + + + + + + + + + + + + + + + Ticket Granting Tickets (TGT) are valid for the lifetime of the SSO Session. They become invalid either + by expiration policy (default 2 hours idle, 8 hours max) or by explicit user sign off via + /cas/login. + The TGT cache can be replicated slowly because TGT are only manipulated via web user started + operations + (mostly grant service ticket) and thus benefit of web session affinity. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-integration-hazelcast/NOTICE b/cas-server-integration-hazelcast/NOTICE new file mode 100644 index 000000000000..c16b97419f53 --- /dev/null +++ b/cas-server-integration-hazelcast/NOTICE @@ -0,0 +1,101 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS REST Implementation under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + CDI APIs under Apache License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-integration-hazelcast/pom.xml b/cas-server-integration-hazelcast/pom.xml new file mode 100644 index 000000000000..f18fd2d042bc --- /dev/null +++ b/cas-server-integration-hazelcast/pom.xml @@ -0,0 +1,49 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-integration-hazelcast + jar + Apereo CAS Hazelcast Integration + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + com.hazelcast + hazelcast + + + + + ${project.parent.basedir} + + + diff --git a/cas-server-integration-hazelcast/src/main/java/org/jasig/cas/ticket/registry/HazelcastTicketRegistry.java b/cas-server-integration-hazelcast/src/main/java/org/jasig/cas/ticket/registry/HazelcastTicketRegistry.java new file mode 100644 index 000000000000..f5e771e6f4ed --- /dev/null +++ b/cas-server-integration-hazelcast/src/main/java/org/jasig/cas/ticket/registry/HazelcastTicketRegistry.java @@ -0,0 +1,170 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.core.IMap; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; + +import java.util.Collection; +import java.util.concurrent.TimeUnit; + +/** + * Hazelcast-based implementation of a {@link TicketRegistry}. + * + *

+ * This implementation just wraps the Hazelcast's {@link IMap} which is an extension of the + * standard Java's ConcurrentMap. The heavy lifting of distributed data partitioning, + * network cluster discovery and join, data replication, etc. is done by Hazelcast's Map implementation. + *

+ * + * @author Dmitriy Kopylenko + * @author Jonathan Johnson + * @since 4.1.0 + */ +public class HazelcastTicketRegistry extends AbstractDistributedTicketRegistry { + + private final IMap registry; + + private final long serviceTicketTimeoutInSeconds; + + private final long ticketGrantingTicketTimoutInSeconds; + + private final HazelcastInstance hz; + + /** + * @param hz An instance of HazelcastInstance + * @param mapName Name of map to use + * @param ticketGrantingTicketTimoutInSeconds TTL for TGT entries + * @param serviceTicketTimeoutInSeconds TTL for ST entries + */ + public HazelcastTicketRegistry(final HazelcastInstance hz, + final String mapName, + final long ticketGrantingTicketTimoutInSeconds, + final long serviceTicketTimeoutInSeconds) { + + logInitialization(hz, mapName, ticketGrantingTicketTimoutInSeconds, serviceTicketTimeoutInSeconds); + this.registry = hz.getMap(mapName); + this.ticketGrantingTicketTimoutInSeconds = ticketGrantingTicketTimoutInSeconds; + this.serviceTicketTimeoutInSeconds = serviceTicketTimeoutInSeconds; + this.hz = hz; + } + + /** + * {@inheritDoc} + */ + @Override + protected void updateTicket(final Ticket ticket) { + addTicket(ticket); + } + + /** + * {@inheritDoc} + */ + @Override + protected boolean needsCallback() { + return false; + } + + /** + * @param hz An instance of HazelcastInstance + * @param mapName Name of map to use + * @param ticketGrantingTicketTimoutInSeconds TTL for TGT entries + * @param serviceTicketTimeoutInSeconds TTL for ST entries + */ + private void logInitialization(final HazelcastInstance hz, + final String mapName, + final long ticketGrantingTicketTimoutInSeconds, + final long serviceTicketTimeoutInSeconds) { + + logger.info("Setting up Hazelcast Ticket Registry..."); + logger.debug("Hazelcast instance: {}", hz); + logger.debug("TGT timeout: [{}s]", ticketGrantingTicketTimoutInSeconds); + logger.debug("ST timeout: [{}s]", serviceTicketTimeoutInSeconds); + } + + /** + * {@inheritDoc} + */ + @Override + public void addTicket(final Ticket ticket) { + addTicket(ticket, getTimeout(ticket)); + } + + /** + * @param ticket a ticket + * @param ttl time to live in seconds + */ + private void addTicket(final Ticket ticket, final long ttl) { + logger.debug("Adding ticket [{}] with ttl [{}s]", ticket.getId(), ttl); + this.registry.set(ticket.getId(), ticket, ttl, TimeUnit.SECONDS); + } + + /** + * {@inheritDoc} + */ + @Override + public Ticket getTicket(final String ticketId) { + return this.registry.get(ticketId); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean deleteTicket(final String ticketId) { + logger.debug("Removing ticket [{}]", ticketId); + return this.registry.remove(ticketId) != null; + } + + /** + * {@inheritDoc} + */ + @Override + public Collection getTickets() { + return this.registry.values(); + } + + /** + * A method to get the starting TTL for a ticket based upon type. + * + * @param t Ticket to get starting TTL for + * + * @return Initial TTL for ticket + */ + private long getTimeout(final Ticket t) { + if (t instanceof TicketGrantingTicket) { + return this.ticketGrantingTicketTimoutInSeconds; + } else if (t instanceof ServiceTicket) { + return this.serviceTicketTimeoutInSeconds; + } + throw new IllegalArgumentException( + String.format("Invalid ticket type [%s]. Expecting either [TicketGrantingTicket] or [ServiceTicket]", + t.getClass().getName())); + } + + /** + * Make sure we shutdown HazelCast when the context is destroyed. + */ + public void shutdown() { + this.hz.shutdown(); + } +} diff --git a/cas-server-integration-hazelcast/src/main/resources/META-INF/spring/hazelcast-ticket-registry.xml b/cas-server-integration-hazelcast/src/main/resources/META-INF/spring/hazelcast-ticket-registry.xml new file mode 100644 index 000000000000..b489cefefe2f --- /dev/null +++ b/cas-server-integration-hazelcast/src/main/resources/META-INF/spring/hazelcast-ticket-registry.xml @@ -0,0 +1,89 @@ + + + + + + + + + + slf4j + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-integration-hazelcast/src/site/site.xml b/cas-server-integration-hazelcast/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-integration-hazelcast/src/site/site.xml @@ -0,0 +1,28 @@ + + + + +

+ + + + diff --git a/cas-server-integration-hazelcast/src/test/java/org/jasig/cas/ticket/registry/HazelcastTicketRegistryTests.java b/cas-server-integration-hazelcast/src/test/java/org/jasig/cas/ticket/registry/HazelcastTicketRegistryTests.java new file mode 100644 index 000000000000..65657c559d42 --- /dev/null +++ b/cas-server-integration-hazelcast/src/test/java/org/jasig/cas/ticket/registry/HazelcastTicketRegistryTests.java @@ -0,0 +1,219 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; + +/** + * Unit tests for @{HazelcastTicketRegistry}. + * + * @author Dmitriy Kopylenko + * @since 4.1.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class HazelcastTicketRegistryTests { + + @Autowired + private HazelcastTicketRegistry hzTicketRegistry1; + + @Autowired + private HazelcastTicketRegistry hzTicketRegistry2; + + public void setHzTicketRegistry1(final HazelcastTicketRegistry hzTicketRegistry1) { + this.hzTicketRegistry1 = hzTicketRegistry1; + } + + public void setHzTicketRegistry2(final HazelcastTicketRegistry hzTicketRegistry2) { + this.hzTicketRegistry2 = hzTicketRegistry2; + } + + @Test + public void basicOperationsAndClustering() throws Exception { + this.hzTicketRegistry1.addTicket(newTestTgt()); + + assertNotNull(this.hzTicketRegistry1.getTicket("TGT-TEST")); + assertNotNull(this.hzTicketRegistry2.getTicket("TGT-TEST")); + assertTrue(this.hzTicketRegistry2.deleteTicket("TGT-TEST")); + assertFalse(this.hzTicketRegistry1.deleteTicket("TGT-TEST")); + assertNull(this.hzTicketRegistry1.getTicket("TGT-TEST")); + assertNull(this.hzTicketRegistry2.getTicket("TGT-TEST")); + + final ServiceTicket st = newTestSt(); + this.hzTicketRegistry2.addTicket(st); + + assertNotNull(this.hzTicketRegistry1.getTicket("ST-TEST")); + assertNotNull(this.hzTicketRegistry2.getTicket("ST-TEST")); + this.hzTicketRegistry1.deleteTicket("ST-TEST"); + assertNull(this.hzTicketRegistry1.getTicket("ST-TEST")); + assertNull(this.hzTicketRegistry2.getTicket("ST-TEST")); + } + + private TicketGrantingTicket newTestTgt() { + return new MockTgt(); + } + + private ServiceTicket newTestSt() { + return new MockSt(); + } + + private static class MockTgt implements TicketGrantingTicket { + + @Override + public Service getProxiedBy() { + return null; + } + + @Override + public Authentication getAuthentication() { + return null; + } + + @Override + public List getSupplementalAuthentications() { + return null; + } + + @Override + public ServiceTicket grantServiceTicket(final String id, + final Service service, + final ExpirationPolicy expirationPolicy, + final boolean credentialsProvided) { + return null; + } + + @Override + public Map getServices() { + return null; + } + + @Override + public void removeAllServices() { + + } + + @Override + public void markTicketExpired() { + + } + + @Override + public boolean isRoot() { + return false; + } + + @Override + public TicketGrantingTicket getRoot() { + return null; + } + + @Override + public List getChainedAuthentications() { + return null; + } + + @Override + public String getId() { + return "TGT-TEST"; + } + + @Override + public boolean isExpired() { + return false; + } + + @Override + public TicketGrantingTicket getGrantingTicket() { + return this; + } + + @Override + public long getCreationTime() { + return 0; + } + + @Override + public int getCountOfUses() { + return 0; + } + } + + private static class MockSt implements ServiceTicket { + @Override + public Service getService() { + return null; + } + + @Override + public boolean isFromNewLogin() { + return false; + } + + @Override + public boolean isValidFor(final Service service) { + return false; + } + + @Override + public TicketGrantingTicket grantTicketGrantingTicket(final String id, + final Authentication authentication, + final ExpirationPolicy expirationPolicy) { + return null; + } + + @Override + public String getId() { + return "ST-TEST"; + } + + @Override + public boolean isExpired() { + return false; + } + + @Override + public TicketGrantingTicket getGrantingTicket() { + return null; + } + + @Override + public long getCreationTime() { + return 0; + } + + @Override + public int getCountOfUses() { + return 0; + } + } +} diff --git a/cas-server-integration-hazelcast/src/test/resources/log4j2.xml b/cas-server-integration-hazelcast/src/test/resources/log4j2.xml new file mode 100644 index 000000000000..f4e2becd12b5 --- /dev/null +++ b/cas-server-integration-hazelcast/src/test/resources/log4j2.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-integration-hazelcast/src/test/resources/org/jasig/cas/ticket/registry/HazelcastTicketRegistryTests-context.xml b/cas-server-integration-hazelcast/src/test/resources/org/jasig/cas/ticket/registry/HazelcastTicketRegistryTests-context.xml new file mode 100644 index 000000000000..3fc3dc535fba --- /dev/null +++ b/cas-server-integration-hazelcast/src/test/resources/org/jasig/cas/ticket/registry/HazelcastTicketRegistryTests-context.xml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + slf4j + true + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + slf4j + true + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cas-server-3.4.2/cas-server-integration-berkeleydb/.cvsignore b/cas-server-integration-jboss/.cvsignore similarity index 100% rename from cas-server-3.4.2/cas-server-integration-berkeleydb/.cvsignore rename to cas-server-integration-jboss/.cvsignore diff --git a/cas-server-integration-jboss/NOTICE b/cas-server-integration-jboss/NOTICE new file mode 100644 index 000000000000..6ea3f4f413fb --- /dev/null +++ b/cas-server-integration-jboss/NOTICE @@ -0,0 +1,105 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS JBoss Cache Integration - DEPRECATED under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + CDI APIs under Apache License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Commons Development and Distribution License, Version 1.0 + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Cache - Core Edition under GNU Lesser General Public License + JBoss Common Classes under lgpl + JBoss Logging 3 under Apache License, version 2.0 + JBoss Logging Programming Interface under lgpl + JGroups under Library (or Lesser) GNU Public License 2.1 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-integration-jboss/pom.xml b/cas-server-integration-jboss/pom.xml new file mode 100644 index 000000000000..98cdcc407b6b --- /dev/null +++ b/cas-server-integration-jboss/pom.xml @@ -0,0 +1,100 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-integration-jboss + jar + Apereo CAS JBoss Cache Integration - DEPRECATED + + The CAS cas-server-integration-jboss module is deprecated and will be removed from subsequent CAS releases. + You should not attempt to include this module in your CAS Maven overlays, and rather try to use alternative + methods for your distributed ticket registry needs, such as the cas-server-integration-ehcache or + cas-server-integration-memcached modules. + + + + + + maven-surefire-plugin + + + true + + + + + + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + org.jboss.cache + jbosscache-core + 2.2.2.GA + jar + + + commons-logging + commons-logging + + + + + + org.jasig.cas + cas-server-core + ${project.version} + test-jar + test + + + + + + jboss-maven2 + default + JBoss Repository + http://repository.jboss.org/nexus/content/repositories/deprecated + + + + jboss + JBoss Repository + default + http://repository.jboss.org/nexus/content/groups/public-jboss/ + + + + + + ${project.parent.basedir} + + diff --git a/cas-server-integration-jboss/src/main/java/org/jasig/cas/ticket/registry/JBossCacheTicketRegistry.java b/cas-server-integration-jboss/src/main/java/org/jasig/cas/ticket/registry/JBossCacheTicketRegistry.java new file mode 100644 index 000000000000..85fe4110c379 --- /dev/null +++ b/cas-server-integration-jboss/src/main/java/org/jasig/cas/ticket/registry/JBossCacheTicketRegistry.java @@ -0,0 +1,136 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import org.jasig.cas.ticket.Ticket; +import org.jboss.cache.Cache; +import org.jboss.cache.CacheException; +import org.jboss.cache.Node; + +import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * @deprecated As of 4.1 the Jboss cache integration module is no longer supported. + * Please use other means of configuring your distributed ticket registry, such as + * ehcache or memcached integrations with CAS. + * + *

Implementation of TicketRegistry that is backed by a JBoss TreeCache. + * @author Scott Battaglia + * @since 3.1 + */ +@Deprecated +public final class JBossCacheTicketRegistry extends AbstractDistributedTicketRegistry { + + /** Indicator of what tree branch to put tickets in. */ + private static final String FQN_TICKET = "ticket"; + + /** Instance of JBoss TreeCache. */ + @NotNull + private Cache cache; + + @Override + protected void updateTicket(final Ticket ticket) { + try { + this.cache.put(FQN_TICKET, ticket.getId(), ticket); + } catch (final CacheException e) { + throw new RuntimeException(e); + } + } + + @Override + public void addTicket(final Ticket ticket) { + try { + logger.debug("Adding ticket to registry for: {}", ticket.getId()); + this.cache.put(FQN_TICKET, ticket.getId(), ticket); + } catch (final CacheException e) { + logger.error(e.getMessage(), e); + throw new RuntimeException(e); + } + } + + @Override + public boolean deleteTicket(final String ticketId) { + try { + logger.debug("Removing ticket from registry for: {}", ticketId); + return this.cache.remove(FQN_TICKET, ticketId) != null; + } catch (final CacheException e) { + logger.error(e.getMessage(), e); + return false; + } + } + + /** + * {@inheritDoc} + * Returns a proxied instance. + * + * @see org.jasig.cas.ticket.registry.TicketRegistry#getTicket(java.lang.String) + */ + @Override + public Ticket getTicket(final String ticketId) { + try { + logger.debug("Retrieving ticket from registry for: {}", ticketId); + return getProxiedTicketInstance(this.cache.get(FQN_TICKET, ticketId)); + } catch (final CacheException e) { + logger.error(e.getMessage(), e); + return null; + } + } + + @Override + public Collection getTickets() { + try { + final Node node = this.cache.getNode(FQN_TICKET); + + if (node == null) { + return Collections.emptyList(); + } + + final Set keys = node.getKeys(); + final List list = new ArrayList<>(); + + for (final String key : keys) { + + /** Returns null if the node contains no mapping for this key. **/ + final Ticket ticket = node.get(key); + + if (ticket != null) { + list.add(node.get(key)); + } + } + + return list; + } catch (final CacheException e) { + return Collections.emptyList(); + } + } + + public void setCache(final Cache cache) { + this.cache = cache; + } + + @Override + protected boolean needsCallback() { + return true; + } +} diff --git a/cas-server-integration-jboss/src/main/java/org/jasig/cas/util/JBossCacheFactoryBean.java b/cas-server-integration-jboss/src/main/java/org/jasig/cas/util/JBossCacheFactoryBean.java new file mode 100644 index 000000000000..bedda40f3b6a --- /dev/null +++ b/cas-server-integration-jboss/src/main/java/org/jasig/cas/util/JBossCacheFactoryBean.java @@ -0,0 +1,81 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import org.jasig.cas.ticket.Ticket; +import org.jboss.cache.Cache; +import org.jboss.cache.CacheFactory; +import org.jboss.cache.DefaultCacheFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Required; +import org.springframework.core.io.Resource; + +/** + * @deprecated As of 4.1 the Jboss cache integration module is no longer supported. + * Please use other means of confguring your distributed ticket registry, such as + * ehcache or memcached integrations with CAS. + * @author Scott Battaglia + * @since 3.0.0.5 + * + */ +@Deprecated +public final class JBossCacheFactoryBean implements FactoryBean, InitializingBean, DisposableBean { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private Cache cache; + + private Resource configLocation; + + @Override + public Object getObject() throws Exception { + return this.cache; + } + + @Override + public Class getObjectType() { + return Cache.class; + } + + @Override + public boolean isSingleton() { + return true; + } + + @Override + public void afterPropertiesSet() throws Exception { + final CacheFactory cf = new DefaultCacheFactory<>(); + this.cache = cf.createCache(this.configLocation.getInputStream()); + } + + @Required + public void setConfigLocation(final Resource configLocation) { + this.configLocation = configLocation; + } + + @Override + public void destroy() throws Exception { + logger.info("Shutting down TreeCache service."); + this.cache.destroy(); + } +} diff --git a/cas-server-integration-jboss/src/site/site.xml b/cas-server-integration-jboss/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-integration-jboss/src/site/site.xml @@ -0,0 +1,28 @@ + + + + +

+ + + + diff --git a/cas-server-3.4.2/cas-server-integration-berkeleydb/src/test/clover/clover.license b/cas-server-integration-jboss/src/test/clover/clover.license similarity index 100% rename from cas-server-3.4.2/cas-server-integration-berkeleydb/src/test/clover/clover.license rename to cas-server-integration-jboss/src/test/clover/clover.license diff --git a/cas-server-integration-jboss/src/test/java/org/jasig/cas/ticket/registry/JBossCacheTicketRegistryTests.java b/cas-server-integration-jboss/src/test/java/org/jasig/cas/ticket/registry/JBossCacheTicketRegistryTests.java new file mode 100644 index 000000000000..3c1249b99aa4 --- /dev/null +++ b/cas-server-integration-jboss/src/test/java/org/jasig/cas/ticket/registry/JBossCacheTicketRegistryTests.java @@ -0,0 +1,259 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import org.apache.commons.io.IOUtils; +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.jboss.cache.Cache; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.util.ArrayList; +import java.util.Collection; + +import static org.junit.Assert.*; + +/** + * @deprecated As of 4.1 the Jboss cache integration module is no longer supported. + * Please use other means of confguring your distributed ticket registry, such as + * ehcache or memcached integrations with CAS. + * + *

Test case to test the DefaultTicketRegistry based on test cases to test all + * Ticket Registries. + * + * either ehcache or memcached modules. + * @author Scott Battaglia + * @author Marc-Antoine Garrigue + * @since 3.0.0 + */ +@Deprecated +public final class JBossCacheTicketRegistryTests { + + private static final String APPLICATION_CONTEXT_FILE_NAME = "jbossTestContext.xml"; + + private static final String APPLICATION_CONTEXT_CACHE_BEAN_NAME = "ticketRegistry"; + + private static final int TICKETS_IN_REGISTRY = 10; + + private JBossCacheTicketRegistry registry; + + private Cache treeCache; + + private TicketRegistry ticketRegistry; + + private ClassPathXmlApplicationContext context; + + @Before + public void setUp() throws Exception { + this.ticketRegistry = this.getNewTicketRegistry(); + } + + @After + public void shutdown() { + IOUtils.closeQuietly(this.context); + } + + public TicketRegistry getNewTicketRegistry() throws Exception { + this.context = new ClassPathXmlApplicationContext( + APPLICATION_CONTEXT_FILE_NAME); + this.registry = (JBossCacheTicketRegistry) context + .getBean(APPLICATION_CONTEXT_CACHE_BEAN_NAME); + + this.treeCache = (Cache) context.getBean("cache"); + this.treeCache.removeNode("/ticket"); + return this.registry; + } + + /** + * Method to add a TicketGrantingTicket to the ticket cache. This should add + * the ticket and return. Failure upon any exception. + */ + @Test + public void verifyAddTicketToCache() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy())); + } catch (final Exception e) { + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyGetNullTicket() { + try { + this.ticketRegistry.getTicket(null, TicketGrantingTicket.class); + } catch (final Exception e) { + fail("Exception caught. None expected."); + } + } + + @Test + public void verifyGetNonExistingTicket() { + try { + this.ticketRegistry.getTicket("FALALALALALAL", + TicketGrantingTicket.class); + } catch (final Exception e) { + fail("Exception caught. None expected."); + } + } + + @Test + public void verifyGetExistingTicketWithProperClass() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy())); + this.ticketRegistry.getTicket("TEST", TicketGrantingTicket.class); + } catch (final Exception e) { + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyGetExistingTicketWithInproperClass() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy())); + this.ticketRegistry.getTicket("TEST", ServiceTicket.class); + } catch (final ClassCastException e) { + return; + } + fail("ClassCastException expected."); + } + + @Test + public void verifyGetNullTicketWithoutClass() { + try { + this.ticketRegistry.getTicket(null); + } catch (final Exception e) { + fail("Exception caught. None expected."); + } + } + + @Test + public void verifyGetNonExistingTicketWithoutClass() { + try { + this.ticketRegistry.getTicket("FALALALALALAL"); + } catch (final Exception e) { + fail("Exception caught. None expected."); + } + } + + @Test + public void verifyGetExistingTicket() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy())); + this.ticketRegistry.getTicket("TEST"); + } catch (final Exception e) { + e.printStackTrace(); + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyDeleteExistingTicket() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy())); + assertTrue("Ticket was not deleted.", this.ticketRegistry + .deleteTicket("TEST")); + } catch (final Exception e) { + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyDeleteNonExistingTicket() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy())); + assertFalse("Ticket was deleted.", this.ticketRegistry + .deleteTicket("TEST1")); + } catch (final Exception e) { + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyDeleteNullTicket() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy())); + assertFalse("Ticket was deleted.", this.ticketRegistry + .deleteTicket(null)); + } catch (final Exception e) { + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyGetTicketsIsZero() { + try { + assertEquals("The size of the empty registry is not zero.", + this.ticketRegistry.getTickets().size(), 0); + } catch (final Exception e) { + e.printStackTrace(); + fail("Caught an exception. But no exception should have been thrown."); + } + } + + @Test + public void verifyGetTicketsFromRegistryEqualToTicketsAdded() { + final Collection tickets = new ArrayList<>(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", "test"); + + for (int i = 0; i < TICKETS_IN_REGISTRY; i++) { + final TicketGrantingTicket ticketGrantingTicket = new TicketGrantingTicketImpl( + "TEST" + i, TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy()); + final ServiceTicket st = ticketGrantingTicket.grantServiceTicket( + "tests" + i, SimpleWebApplicationServiceImpl.createServiceFrom(request), + new NeverExpiresExpirationPolicy(), false); + tickets.add(ticketGrantingTicket); + tickets.add(st); + this.ticketRegistry.addTicket(ticketGrantingTicket); + this.ticketRegistry.addTicket(st); + } + + try { + final Collection ticketRegistryTickets = this.ticketRegistry.getTickets(); + assertEquals( + "The size of the registry is not the same as the collection.", + ticketRegistryTickets.size(), tickets.size()); + + for (final Ticket ticket : tickets) { + if (!ticketRegistryTickets.contains(ticket)) { + fail("Ticket was added to registry but was not found in retrieval of collection of all tickets."); + } + } + } catch (final Exception e) { + fail("Caught an exception. But no exception should have been thrown."); + } + } +} diff --git a/cas-server-3.4.2/cas-server-integration-jboss/src/test/resources/jbossTestCache.xml b/cas-server-integration-jboss/src/test/resources/jbossTestCache.xml similarity index 87% rename from cas-server-3.4.2/cas-server-integration-jboss/src/test/resources/jbossTestCache.xml rename to cas-server-integration-jboss/src/test/resources/jbossTestCache.xml index b55dfc9ed142..41e6889a1597 100644 --- a/cas-server-3.4.2/cas-server-integration-jboss/src/test/resources/jbossTestCache.xml +++ b/cas-server-integration-jboss/src/test/resources/jbossTestCache.xml @@ -1,11 +1,31 @@ - + + @@ -75,7 +95,7 @@ being broken with multicast (even after disabling media sense) set the loopback attribute to true --> @@ -131,8 +151,7 @@ class loader, e.g., inside an application server. Default is "false". --> false - - 130 + 130 + + + + + + + + + diff --git a/cas-server-integration-jboss/src/test/resources/log4j2.xml b/cas-server-integration-jboss/src/test/resources/log4j2.xml new file mode 100644 index 000000000000..8d835324473c --- /dev/null +++ b/cas-server-integration-jboss/src/test/resources/log4j2.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-integration-memcached/NOTICE b/cas-server-integration-memcached/NOTICE new file mode 100644 index 000000000000..009f023f001e --- /dev/null +++ b/cas-server-integration-memcached/NOTICE @@ -0,0 +1,126 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apache Velocity under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS Memcached Integration under Apache 2 + Apereo CAS SAML Server and Validation Support under Apache 2 + ASM Core under BSD + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + Bouncy Castle Provider under Bouncy Castle Licence + CDI APIs under Apache License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Lang under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + ESAPI under BSD or Creative Commons 3.0 BY-SA + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under BSD style + Hibernate Commons Annotations under GNU Lesser General Public License + HttpClient under Apache License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + JCL 1.1.1 implemented over SLF4J under MIT License + jdom under Apache style license + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR 105 - Java(TM) XML Digital Signature API under JDL license + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Kryo under New BSD License + kryo serializers under The Apache Software License, Version 2.0 + Metrics Core under Apache License 2.0 + MinLog under New BSD License + Mockito under The MIT License + Not Yet Commons SSL under Apache License v2 + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + OpenSAML-J under The Apache Software License, Version 2.0 + OpenWS under The Apache Software License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + ReflectASM under New BSD License + Reflections under WTFPL or The New BSD License + serializer under Apache License, Version 2.0 + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + Spymemcached under The Apache Software License, Version 2.0 + Xalan Java under The Apache Software License, Version 2.0 + xercesImpl under Apache License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + XML Commons Resolver Component under The Apache Software License, Version 2.0 + XML Security under The Apache Software License, Version 2.0 + xml-apis under Apache License, Version 2.0 + XMLTooling-J under The Apache Software License, Version 2.0 + diff --git a/cas-server-integration-memcached/pom.xml b/cas-server-integration-memcached/pom.xml new file mode 100644 index 000000000000..8377ff32a029 --- /dev/null +++ b/cas-server-integration-memcached/pom.xml @@ -0,0 +1,103 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-integration-memcached + jar + Apereo CAS Memcached Integration + + + + couchbase + Couchbase Maven Repository + http://files.couchbase.com/maven2 + + + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + org.jasig.cas + cas-server-support-saml + ${project.version} + provided + + + + net.spy + spymemcached + ${spy.memcached.version} + + + + com.esotericsoftware + kryo + ${kryo.version} + + + + de.javakaffee + kryo-serializers + ${kryo.serializers.version} + + + + + de.flapdoodle.embed + de.flapdoodle.embed.memcached + ${embedded.memcached.version} + test + + + + org.jasig.cas + cas-server-core + ${project.version} + test-jar + test + + + + org.hibernate + hibernate-validator + test + + + + + ${project.parent.basedir} + 2.11.7 + 3.0.2 + 0.35 + 1.06.2 + + + diff --git a/cas-server-integration-memcached/src/main/java/org/jasig/cas/monitor/MemcachedMonitor.java b/cas-server-integration-memcached/src/main/java/org/jasig/cas/monitor/MemcachedMonitor.java new file mode 100644 index 000000000000..28f214cd4eed --- /dev/null +++ b/cas-server-integration-memcached/src/main/java/org/jasig/cas/monitor/MemcachedMonitor.java @@ -0,0 +1,106 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import javax.validation.constraints.NotNull; + +import net.spy.memcached.MemcachedClientIF; + +/** + * Monitors the memcached hosts known to an instance of {@link net.spy.memcached.MemcachedClientIF}. + * + * @author Marvin S. Addison + * @since 3.5.1 + */ +public class MemcachedMonitor extends AbstractCacheMonitor { + + @NotNull + private final MemcachedClientIF memcachedClient; + + + /** + * Creates a new monitor that observes the given memcached client. + * + * @param client Memcached client. + */ + public MemcachedMonitor(final MemcachedClientIF client) { + this.memcachedClient = client; + } + + + /** + * Supersede the default cache status algorithm by considering unavailable memcached nodes above cache statistics. + * If all nodes are unavailable, raise an error; if one or more nodes are unavailable, raise a warning; otherwise + * delegate to examination of cache statistics. + * + * @return Cache status descriptor. + */ + public CacheStatus observe() { + if (memcachedClient.getAvailableServers().size() == 0) { + return new CacheStatus(StatusCode.ERROR, "No memcached servers available."); + } + final Collection unavailableList = memcachedClient.getUnavailableServers(); + final CacheStatus status; + if (unavailableList.size() > 0) { + final String description = "One or more memcached servers is unavailable: " + unavailableList; + status = new CacheStatus(StatusCode.WARN, description, getStatistics()); + } else { + status = super.observe(); + } + return status; + } + + + /** + * Get cache statistics for all memcached hosts known to {@link MemcachedClientIF}. + * + * @return Statistics for all available hosts. + */ + protected CacheStatistics[] getStatistics() { + + + final Map> allStats = memcachedClient.getStats(); + final List statsList = new ArrayList<>(); + for (final Map.Entry> entry : allStats.entrySet()) { + final SocketAddress key = entry.getKey(); + final Map statsMap = entry.getValue(); + + if (statsMap.size() > 0) { + final long size = Long.parseLong(statsMap.get("bytes")); + final long capacity = Long.parseLong(statsMap.get("limit_maxbytes")); + final long evictions = Long.parseLong(statsMap.get("evictions")); + + String name; + if (key instanceof InetSocketAddress) { + name = ((InetSocketAddress) key).getHostName(); + } else { + name = key.toString(); + } + statsList.add(new SimpleCacheStatistics(size, capacity, evictions, name)); + } + } + return statsList.toArray(new CacheStatistics[statsList.size()]); + } +} diff --git a/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/MemCacheTicketRegistry.java b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/MemCacheTicketRegistry.java new file mode 100644 index 000000000000..96aa5347b1db --- /dev/null +++ b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/MemCacheTicketRegistry.java @@ -0,0 +1,212 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.TimeUnit; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; + +import net.spy.memcached.AddrUtil; +import net.spy.memcached.MemcachedClient; +import net.spy.memcached.MemcachedClientIF; + +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.springframework.beans.factory.DisposableBean; + +/** + * Key-value ticket registry implementation that stores tickets in memcached keyed on the ticket ID. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.3 + */ +public final class MemCacheTicketRegistry extends AbstractDistributedTicketRegistry implements DisposableBean { + + /** Memcached client. */ + @NotNull + private final MemcachedClientIF client; + + /** + * TGT cache entry timeout in seconds. + */ + @Min(0) + private final int tgtTimeout; + + /** + * ST cache entry timeout in seconds. + */ + @Min(0) + private final int stTimeout; + + + /** + * Creates a new instance that stores tickets in the given memcached hosts. + * + * @param hostnames Array of memcached hosts where each element is of the form host:port. + * @param ticketGrantingTicketTimeOut TGT timeout in seconds. + * @param serviceTicketTimeOut ST timeout in seconds. + */ + public MemCacheTicketRegistry(final String[] hostnames, final int ticketGrantingTicketTimeOut, + final int serviceTicketTimeOut) { + try { + this.client = new MemcachedClient(AddrUtil.getAddresses(Arrays.asList(hostnames))); + } catch (final IOException e) { + throw new IllegalArgumentException("Invalid memcached host specification.", e); + } + this.tgtTimeout = ticketGrantingTicketTimeOut; + this.stTimeout = serviceTicketTimeOut; + } + + /** + * This alternative constructor takes time in milliseconds. + * It has the timeout parameters in order to create a unique method signature. + * + * @param ticketGrantingTicketTimeOut TGT timeout in milliseconds. + * @param serviceTicketTimeOut ST timeout in milliseconds. + * @param hostnames Array of memcached hosts where each element is of the form host:port. + * @see MemCacheTicketRegistry#MemCacheTicketRegistry(String[], int, int) + * @deprecated This has been deprecated + */ + @Deprecated + public MemCacheTicketRegistry(final long ticketGrantingTicketTimeOut, final long serviceTicketTimeOut, + final String[] hostnames) { + this(hostnames, (int) TimeUnit.MILLISECONDS.toSeconds(ticketGrantingTicketTimeOut), + (int) TimeUnit.MILLISECONDS.toSeconds(serviceTicketTimeOut)); + } + + /** + * Creates a new instance using the given memcached client instance, which is presumably configured via + * net.spy.memcached.spring.MemcachedClientFactoryBean. + * + * @param client Memcached client. + * @param ticketGrantingTicketTimeOut TGT timeout in seconds. + * @param serviceTicketTimeOut ST timeout in seconds. + */ + public MemCacheTicketRegistry(final MemcachedClientIF client, final int ticketGrantingTicketTimeOut, + final int serviceTicketTimeOut) { + this.tgtTimeout = ticketGrantingTicketTimeOut; + this.stTimeout = serviceTicketTimeOut; + this.client = client; + } + + @Override + protected void updateTicket(final Ticket ticket) { + logger.debug("Updating ticket {}", ticket); + try { + if (!this.client.replace(ticket.getId(), getTimeout(ticket), ticket).get()) { + logger.error("Failed updating {}", ticket); + } + } catch (final InterruptedException e) { + logger.warn("Interrupted while waiting for response to async replace operation for ticket {}. " + + "Cannot determine whether update was successful.", ticket); + } catch (final Exception e) { + logger.error("Failed updating {}", ticket, e); + } + } + + @Override + public void addTicket(final Ticket ticket) { + logger.debug("Adding ticket {}", ticket); + try { + if (!this.client.add(ticket.getId(), getTimeout(ticket), ticket).get()) { + logger.error("Failed adding {}", ticket); + } + } catch (final InterruptedException e) { + logger.warn("Interrupted while waiting for response to async add operation for ticket {}." + + "Cannot determine whether add was successful.", ticket); + } catch (final Exception e) { + logger.error("Failed adding {}", ticket, e); + } + } + @Override + public boolean deleteTicket(final String ticketId) { + logger.debug("Deleting ticket {}", ticketId); + try { + return this.client.delete(ticketId).get(); + } catch (final Exception e) { + logger.error("Failed deleting {}", ticketId, e); + } + return false; + } + @Override + public Ticket getTicket(final String ticketId) { + try { + final Ticket t = (Ticket) this.client.get(ticketId); + if (t != null) { + return getProxiedTicketInstance(t); + } + } catch (final Exception e) { + logger.error("Failed fetching {} ", ticketId, e); + } + return null; + } + + /** + * {@inheritDoc} + * This operation is not supported. + * + * @throws UnsupportedOperationException if you try and call this operation. + */ + @Override + public Collection getTickets() { + throw new UnsupportedOperationException("GetTickets not supported."); + } + + /** + * Destroy the client and shut down. + * + * @throws Exception the exception + */ + public void destroy() throws Exception { + this.client.shutdown(); + } + + /** + * @param sync set to true, if updates to registry are to be synchronized + * @deprecated As of version 3.5, this operation has no effect since async writes can cause registry consistency issues. + */ + @Deprecated + public void setSynchronizeUpdatesToRegistry(final boolean sync) {} + + @Override + protected boolean needsCallback() { + return true; + } + + /** + * Gets the timeout value for the ticket. + * + * @param t the t + * @return the timeout + */ + private int getTimeout(final Ticket t) { + if (t instanceof TicketGrantingTicket) { + return this.tgtTimeout; + } else if (t instanceof ServiceTicket) { + return this.stTimeout; + } + throw new IllegalArgumentException("Invalid ticket type"); + } +} diff --git a/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/FieldHelper.java b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/FieldHelper.java new file mode 100644 index 000000000000..1a83b83eb6a2 --- /dev/null +++ b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/FieldHelper.java @@ -0,0 +1,92 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry.support.kryo; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +/** + * Helper class that provides convenience methods for getting and setting field values via reflection. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +public final class FieldHelper { + private final Map fieldCache = new HashMap<>(); + + /** + * Gets the field value. + * + * @param target the target + * @param fieldName the field name + * @return the field value + */ + public Object getFieldValue(final Object target, final String fieldName) { + final Field f = getField(target, fieldName); + try { + return f.get(target); + } catch (final IllegalAccessException e) { + throw new IllegalStateException("Error getting field value", e); + } + } + + /** + * Sets the field value. + * + * @param target the target + * @param fieldName the field name + * @param value the value + */ + public void setFieldValue(final Object target, final String fieldName, final Object value) { + final Field f = getField(target, fieldName); + try { + f.set(target, value); + } catch (final IllegalAccessException e) { + throw new IllegalStateException("Error setting field value", e); + } + } + + /** + * Gets the field. + * + * @param target the target + * @param name the name + * @return the field + */ + private Field getField(final Object target, final String name) { + Class clazz = target.getClass(); + final String key = new StringBuilder().append(clazz.getName()).append('.').append(name).toString(); + Field f = fieldCache.get(key); + while (f == null) { + try { + f = clazz.getDeclaredField(name); + f.setAccessible(true); + fieldCache.put(key, f); + } catch (final NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + if (clazz == null) { + throw new IllegalStateException("No such field " + key); + } + } + } + return f; + } + +} diff --git a/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/KryoTranscoder.java b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/KryoTranscoder.java new file mode 100644 index 000000000000..3c4558390d5d --- /dev/null +++ b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/KryoTranscoder.java @@ -0,0 +1,207 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry.support.kryo; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import net.spy.memcached.CachedData; +import net.spy.memcached.transcoders.Transcoder; + +import org.jasig.cas.authentication.BasicCredentialMetaData; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.ImmutableAuthentication; +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.services.RegexRegisteredService; +import org.jasig.cas.services.RegisteredServiceImpl; +import org.jasig.cas.ticket.ServiceTicketImpl; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.jasig.cas.ticket.registry.support.kryo.serial.RegisteredServiceSerializer; +import org.jasig.cas.ticket.registry.support.kryo.serial.SimpleWebApplicationServiceSerializer; +import org.jasig.cas.ticket.registry.support.kryo.serial.URLSerializer; +import org.jasig.cas.ticket.support.HardTimeoutExpirationPolicy; +import org.jasig.cas.ticket.support.MultiTimeUseOrTimeoutExpirationPolicy; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.jasig.cas.ticket.support.RememberMeDelegatingExpirationPolicy; +import org.jasig.cas.ticket.support.ThrottledUseAndTimeoutExpirationPolicy; +import org.jasig.cas.ticket.support.TicketGrantingTicketExpirationPolicy; +import org.jasig.cas.ticket.support.TimeoutExpirationPolicy; +import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import com.esotericsoftware.kryo.serializers.DefaultSerializers; + +import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer; +import de.javakaffee.kryoserializers.jodatime.JodaDateTimeSerializer; +import org.slf4j.impl.CasDelegatingLogger; + +/** + * {@link net.spy.memcached.MemcachedClient} transcoder implementation based on Kryo fast serialization framework + * suited for efficient serialization of tickets. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +@SuppressWarnings("rawtypes") +public class KryoTranscoder implements Transcoder { + + /** Kryo serializer. */ + private final Kryo kryo = new Kryo(); + + /** Logging instance. */ + private final Logger logger = LoggerFactory.getLogger(getClass()); + + /** Map of class to serializer that handles it. */ + private Map, Serializer> serializerMap; + + /** + * Creates a Kryo-based transcoder. + */ + public KryoTranscoder() { + } + + /** + * @deprecated + * Creates a Kryo-based transcoder. + * + * @param initialBufferSize Initial size for buffer holding encoded object data. + */ + @Deprecated + public KryoTranscoder(final int initialBufferSize) { + logger.warn("It's no longer necessary to define the initialBufferSize. Use the empty constructor."); + } + + /** + * Sets a map of additional types that should be regisetered with Kryo, + * for example GoogleAccountsService and OpenIdService. + * + * @param map Map of class to the serializer instance that handles it. + */ + public void setSerializerMap(final Map, Serializer> map) { + this.serializerMap = map; + } + + /** + * Initialize and register classes with kryo. + */ + public void initialize() { + // Register types we know about and do not require external configuration + kryo.register(ArrayList.class); + kryo.register(BasicCredentialMetaData.class); + kryo.register(Class.class, new DefaultSerializers.ClassSerializer()); + kryo.register(Date.class, new DefaultSerializers.DateSerializer()); + kryo.register(HardTimeoutExpirationPolicy.class); + kryo.register(HashMap.class); + kryo.register(DefaultHandlerResult.class); + kryo.register(ImmutableAuthentication.class); + kryo.register(MultiTimeUseOrTimeoutExpirationPolicy.class); + kryo.register(NeverExpiresExpirationPolicy.class); + kryo.register(RememberMeDelegatingExpirationPolicy.class); + kryo.register(ServiceTicketImpl.class); + kryo.register(SimpleWebApplicationServiceImpl.class, new SimpleWebApplicationServiceSerializer()); + kryo.register(ThrottledUseAndTimeoutExpirationPolicy.class); + kryo.register(TicketGrantingTicketExpirationPolicy.class); + kryo.register(TicketGrantingTicketImpl.class); + kryo.register(TimeoutExpirationPolicy.class); + kryo.register(URL.class, new URLSerializer()); + + // we add these ones for tests only + kryo.register(RegisteredServiceImpl.class, new RegisteredServiceSerializer()); + kryo.register(RegexRegisteredService.class, new RegisteredServiceSerializer()); + + // new serializers to manage Joda dates and immutable collections + kryo.register(DateTime.class, new JodaDateTimeSerializer()); + kryo.register(CasDelegatingLogger.class, new DefaultSerializers.VoidSerializer()); + + // from the kryo-serializers library (https://github.com/magro/kryo-serializers) + UnmodifiableCollectionsSerializer.registerSerializers(kryo); + + // Register other types + if (serializerMap != null) { + for (final Map.Entry, Serializer> clazz : serializerMap.entrySet()) { + kryo.register(clazz.getKey(), clazz.getValue()); + } + } + + // don't reinit the registered classes after every write or read + kryo.setAutoReset(false); + // don't replace objects by references + kryo.setReferences(false); + // Catchall for any classes not explicitly registered + kryo.setRegistrationRequired(false); + } + + /** + * Asynchronous decoding is not supported. + * + * @param d Data to decode. + * @return False. + */ + public boolean asyncDecode(final CachedData d) { + return false; + } + + @Override + public CachedData encode(final Object obj) { + final ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + try (final Output output = new Output(byteStream)) { + kryo.writeClassAndObject(output, obj); + output.flush(); + final byte[] bytes = byteStream.toByteArray(); + return new CachedData(0, bytes, bytes.length); + } + } + + @Override + public Object decode(final CachedData d) { + final byte[] bytes = d.getData(); + try (final Input input = new Input(new ByteArrayInputStream(bytes))) { + final Object obj = kryo.readClassAndObject(input); + return obj; + } + } + + /** + * Maximum size of encoded data supported by this transcoder. + * + * @return net.spy.memcached.CachedData#MAX_SIZE. + */ + public int getMaxSize() { + return CachedData.MAX_SIZE; + } + + /** + * Gets the kryo object that provides encoding and decoding services for this instance. + * + * @return Underlying Kryo instance. + */ + public Kryo getKryo() { + return kryo; + } +} diff --git a/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/AbstractWebApplicationServiceSerializer.java b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/AbstractWebApplicationServiceSerializer.java new file mode 100644 index 000000000000..a5e89fdcb189 --- /dev/null +++ b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/AbstractWebApplicationServiceSerializer.java @@ -0,0 +1,76 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry.support.kryo.serial; + +import org.jasig.cas.authentication.principal.AbstractWebApplicationService; +import org.jasig.cas.ticket.registry.support.kryo.FieldHelper; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +/** + * Serializer for classes that extend {@link org.jasig.cas.authentication.principal.AbstractWebApplicationService}. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +public abstract class AbstractWebApplicationServiceSerializer + extends Serializer { + /** FieldHelper instance. **/ + protected final FieldHelper fieldHelper; + + /** + * Instantiates a new abstract web application service serializer. + * + * @param helper the helper + */ + public AbstractWebApplicationServiceSerializer(final FieldHelper helper) { + this.fieldHelper = helper; + } + + @Override + public void write(final Kryo kryo, final Output output, final T service) { + kryo.writeObject(output, service.getId()); + kryo.writeObject(output, fieldHelper.getFieldValue(service, "originalUrl")); + kryo.writeObject(output, service.getArtifactId()); + } + + @Override + public T read(final Kryo kryo, final Input input, final Class type) { + return createService(kryo, input, + kryo.readObject(input, String.class), + kryo.readObject(input, String.class), + kryo.readObject(input, String.class)); + } + + /** + * Creates the service. + * + * @param kryo the Kryo instance + * @param input the input stream representing the serialized object + * @param id the id + * @param originalUrl the original url + * @param artifactId the artifact id + * @return the created service instance. + */ + protected abstract T createService(final Kryo kryo, final Input input, final String id, + final String originalUrl, final String artifactId); +} diff --git a/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/GoogleAccountsServiceSerializer.java b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/GoogleAccountsServiceSerializer.java new file mode 100644 index 000000000000..7f31834d12b6 --- /dev/null +++ b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/GoogleAccountsServiceSerializer.java @@ -0,0 +1,110 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry.support.kryo.serial; + +import java.lang.reflect.Constructor; +import java.security.PrivateKey; +import java.security.PublicKey; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +import org.jasig.cas.support.saml.authentication.principal.GoogleAccountsService; +import org.jasig.cas.ticket.registry.support.kryo.FieldHelper; + +/** + * Serializer for {@link GoogleAccountsService}. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +public final class GoogleAccountsServiceSerializer extends AbstractWebApplicationServiceSerializer { + + private static final Constructor CONSTRUCTOR; + + private final PrivateKey privateKey; + private final PublicKey publicKey; + private final String alternateUsername; + + static { + try { + CONSTRUCTOR = GoogleAccountsService.class.getDeclaredConstructor( + String.class, + String.class, + String.class, + String.class, + String.class, + PrivateKey.class, + PublicKey.class, + String.class); + CONSTRUCTOR.setAccessible(true); + } catch (final NoSuchMethodException e) { + throw new IllegalStateException("Expected constructor signature not found.", e); + } + } + + /** + * Instantiates a new google accounts service serializer. + * + * @param helper the helper + * @param publicKey the public key + * @param privateKey the private key + * @param alternateUsername the alternate username + */ + public GoogleAccountsServiceSerializer( + final FieldHelper helper, + final PublicKey publicKey, + final PrivateKey privateKey, + final String alternateUsername) { + + super(helper); + this.publicKey = publicKey; + this.privateKey = privateKey; + this.alternateUsername = alternateUsername; + } + + @Override + public void write(final Kryo kryo, final Output output, final GoogleAccountsService service) { + super.write(kryo, output, service); + kryo.writeObject(output, fieldHelper.getFieldValue(service, "requestId")); + kryo.writeObject(output, fieldHelper.getFieldValue(service, "relayState")); + } + + @Override + protected GoogleAccountsService createService(final Kryo kryo, final Input input, final String id, + final String originalUrl, final String artifactId) { + + final String requestId = kryo.readObject(input, String.class); + final String relayState = kryo.readObject(input, String.class); + try { + return (GoogleAccountsService) CONSTRUCTOR.newInstance( + id, + originalUrl, + artifactId, + relayState, + requestId, + privateKey, + publicKey, + alternateUsername); + } catch (final Exception e) { + throw new IllegalStateException("Error creating SamlService", e); + } + } +} diff --git a/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/RegisteredServiceSerializer.java b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/RegisteredServiceSerializer.java new file mode 100644 index 000000000000..3a4543ad24eb --- /dev/null +++ b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/RegisteredServiceSerializer.java @@ -0,0 +1,49 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry.support.kryo.serial; + +import org.jasig.cas.services.AbstractRegisteredService; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.RegisteredServiceImpl; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +/** + * Serializier for {@link org.jasig.cas.services.RegisteredService} instances. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class RegisteredServiceSerializer extends Serializer { + + @Override + public void write(final Kryo kryo, final Output output, final RegisteredService service) { + kryo.writeObject(output, service.getServiceId()); + } + + @Override + public RegisteredService read(final Kryo kryo, final Input input, final Class type) { + final String id = kryo.readObject(input, String.class); + final AbstractRegisteredService svc = new RegisteredServiceImpl(); + svc.setServiceId(id); + return svc; + } +} diff --git a/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/SamlServiceSerializer.java b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/SamlServiceSerializer.java new file mode 100644 index 000000000000..5922de2e9f60 --- /dev/null +++ b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/SamlServiceSerializer.java @@ -0,0 +1,78 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry.support.kryo.serial; + +import java.lang.reflect.Constructor; + +import org.jasig.cas.support.saml.authentication.principal.SamlService; +import org.jasig.cas.ticket.registry.support.kryo.FieldHelper; +import org.jasig.cas.util.http.HttpClient; +import org.jasig.cas.util.http.SimpleHttpClientFactoryBean; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +/** + * Serializer for {@link SamlService} class. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +public final class SamlServiceSerializer extends AbstractWebApplicationServiceSerializer { + private static final Constructor CONSTRUCTOR; + + static { + try { + CONSTRUCTOR = SamlService.class.getDeclaredConstructor( + String.class, String.class, String.class, HttpClient.class, String.class); + CONSTRUCTOR.setAccessible(true); + } catch (final NoSuchMethodException e) { + throw new IllegalStateException("Expected constructor signature not found.", e); + } + } + + /** + * Instantiates a new SAML service serializer. + * + * @param helper the helper + */ + public SamlServiceSerializer(final FieldHelper helper) { + super(helper); + } + + @Override + public void write(final Kryo kryo, final Output output, final SamlService service) { + super.write(kryo, output, service); + kryo.writeObject(output, service.getRequestID()); + } + + @Override + protected SamlService createService(final Kryo kryo, final Input input, final String id, + final String originalUrl, final String artifactId) { + + final String requestId = kryo.readObject(input, String.class); + try { + return (SamlService) CONSTRUCTOR.newInstance(id, originalUrl, artifactId, new SimpleHttpClientFactoryBean().getObject(), + requestId); + } catch (final Exception e) { + throw new IllegalStateException("Error creating SamlService", e); + } + } +} diff --git a/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/SimpleWebApplicationServiceSerializer.java b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/SimpleWebApplicationServiceSerializer.java new file mode 100644 index 000000000000..de246ab8476c --- /dev/null +++ b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/SimpleWebApplicationServiceSerializer.java @@ -0,0 +1,45 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry.support.kryo.serial; + +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +/** + * Serializer for {@link SimpleWebApplicationServiceImpl} class. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +public final class SimpleWebApplicationServiceSerializer extends Serializer { + + @Override + public void write(final Kryo kryo, final Output output, final SimpleWebApplicationServiceImpl service) { + kryo.writeObject(output, service.getId()); + } + + @Override + public SimpleWebApplicationServiceImpl read(final Kryo kryo, final Input input, final Class type) { + return new SimpleWebApplicationServiceImpl(kryo.readObject(input, String.class)); + } +} diff --git a/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/URLSerializer.java b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/URLSerializer.java new file mode 100644 index 000000000000..e1daa89577ab --- /dev/null +++ b/cas-server-integration-memcached/src/main/java/org/jasig/cas/ticket/registry/support/kryo/serial/URLSerializer.java @@ -0,0 +1,51 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry.support.kryo.serial; + +import java.net.MalformedURLException; +import java.net.URL; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +/** + * Kryo serializer for {@link URL}. + * + * @author Jerome Leleu + * @since 4.0.0 + */ +public final class URLSerializer extends Serializer { + + @Override + public URL read(final Kryo kryo, final Input input, final Class type) { + final String url = kryo.readObject(input, String.class); + try { + return new URL(url); + } catch (final MalformedURLException e) { + throw new RuntimeException(e); + } + } + + @Override + public void write(final Kryo kryo, final Output output, final URL url) { + kryo.writeObject(output, url.toExternalForm()); + } +} diff --git a/cas-server-integration-memcached/src/site/site.xml b/cas-server-integration-memcached/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-integration-memcached/src/site/site.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/cas-server-integration-memcached/src/test/java/org/jasig/cas/ticket/registry/MemCacheTicketRegistryTests.java b/cas-server-integration-memcached/src/test/java/org/jasig/cas/ticket/registry/MemCacheTicketRegistryTests.java new file mode 100644 index 000000000000..0a4379ae769d --- /dev/null +++ b/cas-server-integration-memcached/src/test/java/org/jasig/cas/ticket/registry/MemCacheTicketRegistryTests.java @@ -0,0 +1,171 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import de.flapdoodle.embed.memcached.Command; +import de.flapdoodle.embed.memcached.MemcachedExecutable; +import de.flapdoodle.embed.memcached.MemcachedProcess; +import de.flapdoodle.embed.memcached.MemcachedStarter; +import de.flapdoodle.embed.memcached.config.ArtifactStoreBuilder; +import de.flapdoodle.embed.memcached.config.DownloadConfigBuilder; +import de.flapdoodle.embed.memcached.config.MemcachedConfig; +import de.flapdoodle.embed.memcached.config.RuntimeConfigBuilder; +import de.flapdoodle.embed.memcached.distribution.Version; +import de.flapdoodle.embed.process.config.store.IDownloadConfig; +import de.flapdoodle.embed.process.io.progress.StandardConsoleProgressListener; +import org.jasig.cas.ticket.ServiceTicket; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Assume; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.io.IOException; +import java.net.Socket; +import java.util.Arrays; +import java.util.Collection; + +import static org.mockito.Mockito.*; + +/** + * Unit test for MemCacheTicketRegistry class. + * + * @author Middleware Services + * @since 3.0.0 + */ +@RunWith(Parameterized.class) +public class MemCacheTicketRegistryTests { + + private static MemcachedExecutable MEMCACHED_EXECUTABLE; + private static MemcachedProcess MEMCACHED; + + private ApplicationContext context; + + private MemCacheTicketRegistry registry; + + private final String registryBean; + + private final boolean binaryProtocol; + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + public MemCacheTicketRegistryTests(final String beanName, final boolean binary) { + registryBean = beanName; + binaryProtocol = binary; + } + + @Parameterized.Parameters + public static Collection getTestParameters() throws Exception { + return Arrays.asList(new Object[] {"testCase1", false}, new Object[] {"testCase2", true}); + } + + @BeforeClass + public static void beforeClass() throws IOException { + final MemcachedStarter runtime = MemcachedStarter.getInstance( + new CasRuntimeConfigBuilder().defaults(Command.MemcacheD).build()); + MEMCACHED_EXECUTABLE = runtime.prepare(new MemcachedConfig(Version.V1_4_22, 11211)); + MEMCACHED = MEMCACHED_EXECUTABLE.start(); + } + + @AfterClass + public static void afterClass() throws Exception { + if (MEMCACHED != null && MEMCACHED.isProcessRunning()) { + MEMCACHED.stop(); + } + if (MEMCACHED_EXECUTABLE != null) { + MEMCACHED_EXECUTABLE.stop(); + } + } + + @Before + public void setUp() throws IOException { + + // Abort tests if there is no memcached server available on localhost:11211. + final boolean environmentOk = isMemcachedListening(); + if (!environmentOk) { + logger.warn("Aborting test since no memcached server is available on localhost."); + } + Assume.assumeTrue(environmentOk); + context = new ClassPathXmlApplicationContext("/ticketRegistry-test.xml"); + registry = context.getBean(registryBean, MemCacheTicketRegistry.class); + } + + @Test + public void verifyWriteGetDelete() throws Exception { + final String id = "ST-1234567890ABCDEFGHIJKL-crud"; + final ServiceTicket ticket = mock(ServiceTicket.class, withSettings().serializable()); + when(ticket.getId()).thenReturn(id); + registry.addTicket(ticket); + final ServiceTicket ticketFromRegistry = (ServiceTicket) registry.getTicket(id); + Assert.assertNotNull(ticketFromRegistry); + Assert.assertEquals(id, ticketFromRegistry.getId()); + registry.deleteTicket(id); + Assert.assertNull(registry.getTicket(id)); + } + + @Test + public void verifyExpiration() throws Exception { + final String id = "ST-1234567890ABCDEFGHIJKL-exp"; + final ServiceTicket ticket = mock(ServiceTicket.class, withSettings().serializable()); + when(ticket.getId()).thenReturn(id); + registry.addTicket(ticket); + Assert.assertNotNull(registry.getTicket(id, ServiceTicket.class)); + // Sleep a little longer than service ticket expiry defined in Spring context + Thread.sleep(2100); + Assert.assertNull(registry.getTicket(id, ServiceTicket.class)); + } + + private boolean isMemcachedListening() { + try (Socket socket = new Socket("127.0.0.1", 11211)) { + return true; + } catch (final Exception e) { + return false; + } + } + + private static class CasRuntimeConfigBuilder extends RuntimeConfigBuilder { + @Override + public RuntimeConfigBuilder defaults(final Command command) { + final RuntimeConfigBuilder builder = super.defaults(command); + + final IDownloadConfig downloadConfig = new CasDownloadConfigBuilder() + .defaultsForCommand(command) + .progressListener(new StandardConsoleProgressListener()) + .build(); + this.artifactStore().overwriteDefault((new ArtifactStoreBuilder()).defaults(command).download(downloadConfig).build()); + return builder; + } + } + + private static class CasDownloadConfigBuilder extends DownloadConfigBuilder { + @Override + public DownloadConfigBuilder defaults() { + final DownloadConfigBuilder bldr = super.defaults(); + bldr.downloadPath("http://heli0s.darktech.org/memcached/"); + return bldr; + } + } +} diff --git a/cas-server-integration-memcached/src/test/java/org/jasig/cas/ticket/registry/support/kryo/KryoTranscoderTests.java b/cas-server-integration-memcached/src/test/java/org/jasig/cas/ticket/registry/support/kryo/KryoTranscoderTests.java new file mode 100644 index 000000000000..237006977b17 --- /dev/null +++ b/cas-server-integration-memcached/src/test/java/org/jasig/cas/ticket/registry/support/kryo/KryoTranscoderTests.java @@ -0,0 +1,458 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry.support.kryo; + +import static org.junit.Assert.assertEquals; + +import java.net.MalformedURLException; +import java.net.URL; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.security.auth.login.AccountNotFoundException; +import javax.security.auth.login.FailedLoginException; +import javax.validation.constraints.NotNull; + +import net.spy.memcached.CachedData; +import org.apache.commons.collections4.map.ListOrderedMap; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.AcceptUsersAuthenticationHandler; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.AuthenticationBuilder; +import org.jasig.cas.authentication.DefaultAuthenticationBuilder; +import org.jasig.cas.authentication.AuthenticationHandler; +import org.jasig.cas.authentication.BasicCredentialMetaData; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.CredentialMetaData; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.HttpBasedServiceCredential; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.RememberMeCredential; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.jasig.cas.authentication.principal.PrincipalFactory; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.services.RegisteredServiceImpl; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.junit.Test; + +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.serializers.FieldSerializer; + +/** + * Unit test for {@link KryoTranscoder} class. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +@SuppressWarnings("rawtypes") +public class KryoTranscoderTests { + + private static final String ST_ID = "ST-1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890ABCDEFGHIJK"; + private static final String TGT_ID = "TGT-1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890ABCDEFGHIJK-cas1"; + + private static final String USERNAME = "handymanbob"; + private static final String PASSWORD = "foo"; + private static final String NICKNAME_KEY = "nickname"; + private static final String NICKNAME_VALUE = "bob"; + + private final KryoTranscoder transcoder; + + private final Map principalAttributes; + + public KryoTranscoderTests() { + transcoder = new KryoTranscoder(); + final Map, Serializer> serializerMap = new HashMap, Serializer>(); + serializerMap.put( + MockServiceTicket.class, + new FieldSerializer(transcoder.getKryo(), MockServiceTicket.class)); + serializerMap.put( + MockTicketGrantingTicket.class, + new FieldSerializer(transcoder.getKryo(), MockTicketGrantingTicket.class)); + transcoder.setSerializerMap(serializerMap); + transcoder.initialize(); + + this.principalAttributes = new HashMap<>(); + this.principalAttributes.put(NICKNAME_KEY, NICKNAME_VALUE); + } + + @Test + public void verifyEncodeDecodeTGTImpl() throws Exception { + final Credential userPassCredential = new UsernamePasswordCredential(USERNAME, PASSWORD); + final AuthenticationBuilder bldr = new DefaultAuthenticationBuilder( + new DefaultPrincipalFactory() + .createPrincipal("user", Collections.unmodifiableMap(this.principalAttributes))); + bldr.setAttributes(Collections.unmodifiableMap(this.principalAttributes)); + bldr.setAuthenticationDate(new Date()); + bldr.addCredential(new BasicCredentialMetaData(userPassCredential)); + bldr.addFailure("error", AccountNotFoundException.class); + bldr.addSuccess("authn", new DefaultHandlerResult( + new AcceptUsersAuthenticationHandler(), + new BasicCredentialMetaData(userPassCredential))); + + final TicketGrantingTicket parent = + new TicketGrantingTicketImpl(TGT_ID, TestUtils.getService(), null, bldr.build(), + new NeverExpiresExpirationPolicy()); + + final TicketGrantingTicket expectedTGT = + new TicketGrantingTicketImpl(TGT_ID, TestUtils.getService(), + null, bldr.build(), + new NeverExpiresExpirationPolicy()); + + final ServiceTicket ticket = expectedTGT.grantServiceTicket(ST_ID, + TestUtils.getService(), + new NeverExpiresExpirationPolicy(), false); + CachedData result = transcoder.encode(expectedTGT); + final TicketGrantingTicket resultTicket = (TicketGrantingTicket) transcoder.decode(result); + + assertEquals(expectedTGT, resultTicket); + result = transcoder.encode(ticket); + final ServiceTicket resultStTicket = (ServiceTicket) transcoder.decode(result); + assertEquals(ticket, resultStTicket); + + } + + @Test + public void verifyEncodeDecode() throws Exception { + final ServiceTicket expectedST = + new MockServiceTicket(ST_ID); + assertEquals(expectedST, transcoder.decode(transcoder.encode(expectedST))); + + final Credential userPassCredential = new UsernamePasswordCredential(USERNAME, PASSWORD); + final TicketGrantingTicket expectedTGT = new MockTicketGrantingTicket(TGT_ID, userPassCredential, this.principalAttributes); + expectedTGT.grantServiceTicket(ST_ID, null, null, false); + assertEquals(expectedTGT, transcoder.decode(transcoder.encode(expectedTGT))); + + internalProxyTest("http://localhost"); + internalProxyTest("https://localhost:8080/path/file.html?p1=v1&p2=v2#fragment"); + } + + private void internalProxyTest(final String proxyUrl) throws MalformedURLException { + final RegisteredServiceImpl svc = new RegisteredServiceImpl(); + svc.setServiceId("https://some.app.edu"); + final Credential proxyCredential = new HttpBasedServiceCredential(new URL(proxyUrl), svc); + final TicketGrantingTicket expectedTGT = new MockTicketGrantingTicket(TGT_ID, proxyCredential, this.principalAttributes); + expectedTGT.grantServiceTicket(ST_ID, null, null, false); + assertEquals(expectedTGT, transcoder.decode(transcoder.encode(expectedTGT))); + } + + @Test + public void verifyEncodeDecodeTGTWithUnmodifiableMap() throws Exception { + final Credential userPassCredential = new UsernamePasswordCredential(USERNAME, PASSWORD); + final TicketGrantingTicket expectedTGT = + new MockTicketGrantingTicket(TGT_ID, userPassCredential, Collections.unmodifiableMap(this.principalAttributes)); + expectedTGT.grantServiceTicket(ST_ID, null, null, false); + assertEquals(expectedTGT, transcoder.decode(transcoder.encode(expectedTGT))); + } + + @Test + public void verifyEncodeDecodeTGTWithUnmodifiableList() throws Exception { + final Credential userPassCredential = new UsernamePasswordCredential(USERNAME, PASSWORD); + final List values = new ArrayList<>(); + values.add(NICKNAME_VALUE); + final Map newAttributes = new HashMap<>(); + newAttributes.put(NICKNAME_KEY, Collections.unmodifiableList(values)); + final TicketGrantingTicket expectedTGT = new MockTicketGrantingTicket(TGT_ID, userPassCredential, newAttributes); + expectedTGT.grantServiceTicket(ST_ID, null, null, false); + assertEquals(expectedTGT, transcoder.decode(transcoder.encode(expectedTGT))); + } + + @Test + public void verifyEncodeDecodeTGTWithLinkedHashMap() throws Exception { + final Credential userPassCredential = new UsernamePasswordCredential(USERNAME, PASSWORD); + final TicketGrantingTicket expectedTGT = + new MockTicketGrantingTicket(TGT_ID, userPassCredential, new LinkedHashMap(this.principalAttributes)); + expectedTGT.grantServiceTicket(ST_ID, null, null, false); + assertEquals(expectedTGT, transcoder.decode(transcoder.encode(expectedTGT))); + } + + @Test + public void verifyEncodeDecodeTGTWithListOrderedMap() throws Exception { + final Credential userPassCredential = new UsernamePasswordCredential(USERNAME, PASSWORD); + @SuppressWarnings("unchecked") + final TicketGrantingTicket expectedTGT = + new MockTicketGrantingTicket(TGT_ID, userPassCredential, ListOrderedMap.listOrderedMap(this.principalAttributes)); + expectedTGT.grantServiceTicket(ST_ID, null, null, false); + assertEquals(expectedTGT, transcoder.decode(transcoder.encode(expectedTGT))); + } + + @Test + public void verifyEncodeDecodeTGTWithUnmodifiableSet() throws Exception { + final Map newAttributes = new HashMap<>(); + final Set values = new HashSet<>(); + values.add(NICKNAME_VALUE); + newAttributes.put(NICKNAME_KEY, Collections.unmodifiableSet(values)); + final Credential userPassCredential = new UsernamePasswordCredential(USERNAME, PASSWORD); + final TicketGrantingTicket expectedTGT = new MockTicketGrantingTicket(TGT_ID, userPassCredential, newAttributes); + expectedTGT.grantServiceTicket(ST_ID, null, null, false); + assertEquals(expectedTGT, transcoder.decode(transcoder.encode(expectedTGT))); + } + + @Test + public void verifyEncodeDecodeTGTWithSingleton() throws Exception { + final Map newAttributes = new HashMap<>(); + newAttributes.put(NICKNAME_KEY, Collections.singleton(NICKNAME_VALUE)); + final Credential userPassCredential = new UsernamePasswordCredential(USERNAME, PASSWORD); + final TicketGrantingTicket expectedTGT = new MockTicketGrantingTicket(TGT_ID, userPassCredential, newAttributes); + expectedTGT.grantServiceTicket(ST_ID, null, null, false); + assertEquals(expectedTGT, transcoder.decode(transcoder.encode(expectedTGT))); + } + + @Test + public void verifyEncodeDecodeTGTWithSingletonMap() throws Exception { + final Map newAttributes = Collections.singletonMap(NICKNAME_KEY, (Object) NICKNAME_VALUE); + final Credential userPassCredential = new UsernamePasswordCredential(USERNAME, PASSWORD); + final TicketGrantingTicket expectedTGT = new MockTicketGrantingTicket(TGT_ID, userPassCredential, newAttributes); + expectedTGT.grantServiceTicket(ST_ID, null, null, false); + assertEquals(expectedTGT, transcoder.decode(transcoder.encode(expectedTGT))); + } + + static class MockServiceTicket implements ServiceTicket { + + private static final long serialVersionUID = -206395373480723831L; + private String id; + + MockServiceTicket() { /* for serialization */ } + + public MockServiceTicket(final String id) { + this.id = id; + } + + public Service getService() { + return null; + } + + public boolean isFromNewLogin() { + return false; + } + + public boolean isValidFor(final Service service) { + return false; + } + + public TicketGrantingTicket grantTicketGrantingTicket(final String id, final Authentication authentication, + final ExpirationPolicy expirationPolicy) { + return null; + } + + public String getId() { + return id; + } + + public boolean isExpired() { + return false; + } + + public TicketGrantingTicket getGrantingTicket() { + return null; + } + + public long getCreationTime() { + return 0; + } + + public int getCountOfUses() { + return 0; + } + + @Override + public boolean equals(final Object other) { + return other instanceof MockServiceTicket && ((MockServiceTicket) other).getId().equals(id); + } + + @Override + public int hashCode() { + final HashCodeBuilder bldr = new HashCodeBuilder(17, 33); + return bldr.append(this.id) + .toHashCode(); + } + } + + static class MockTicketGrantingTicket implements TicketGrantingTicket { + + private static final long serialVersionUID = 4829406617873497061L; + + private final String id; + + private int usageCount; + + private Service proxiedBy; + + private final Date creationDate = new Date(); + + private final Authentication authentication; + + /** Factory to create the principal type. **/ + @NotNull + private final PrincipalFactory principalFactory = new DefaultPrincipalFactory(); + + /** Constructor for serialization support. */ + MockTicketGrantingTicket() { + this.id = null; + this.authentication = null; + } + + public MockTicketGrantingTicket(final String id, final Credential credential, final Map principalAttributes) { + this.id = id; + final CredentialMetaData credentialMetaData = new BasicCredentialMetaData(credential); + final DefaultAuthenticationBuilder builder = new DefaultAuthenticationBuilder(); + builder.setPrincipal(this.principalFactory.createPrincipal(USERNAME, principalAttributes)); + builder.setAuthenticationDate(new Date()); + builder.addCredential(credentialMetaData); + builder.addAttribute(RememberMeCredential.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME, Boolean.TRUE); + final AuthenticationHandler handler = new MockAuthenticationHandler(); + try { + builder.addSuccess(handler.getName(), handler.authenticate(credential)); + } catch (final Exception e) { + throw new RuntimeException(e); + } + builder.addFailure(handler.getName(), FailedLoginException.class); + this.authentication = builder.build(); + } + + @Override + public Authentication getAuthentication() { + return this.authentication; + } + + @Override + public List getSupplementalAuthentications() { + return Collections.emptyList(); + } + + @Override + public ServiceTicket grantServiceTicket( + final String id, + final Service service, + final ExpirationPolicy expirationPolicy, + final boolean credentialsProvided) { + this.usageCount++; + return new MockServiceTicket(id); + } + + @Override + public Service getProxiedBy() { + return proxiedBy; + } + + @Override + public Map getServices() { + return Collections.emptyMap(); + } + + @Override + public void removeAllServices() {} + + @Override + public void markTicketExpired() {} + + @Override + public boolean isRoot() { + return true; + } + + @Override + public TicketGrantingTicket getRoot() { + return this; + } + + @Override + public List getChainedAuthentications() { + return Collections.emptyList(); + } + + @Override + public String getId() { + return this.id; + } + + @Override + public boolean isExpired() { + return false; + } + + @Override + public TicketGrantingTicket getGrantingTicket() { + return this; + } + + @Override + public long getCreationTime() { + return this.creationDate.getTime(); + } + + @Override + public int getCountOfUses() { + return this.usageCount; + } + + @Override + public boolean equals(final Object other) { + return other instanceof MockTicketGrantingTicket + && ((MockTicketGrantingTicket) other).getId().equals(this.id) + && ((MockTicketGrantingTicket) other).getCountOfUses() == this.usageCount + && ((MockTicketGrantingTicket) other).getCreationTime() == this.creationDate.getTime() + && ((MockTicketGrantingTicket) other).getAuthentication().equals(this.authentication); + } + + @Override + public int hashCode() { + final HashCodeBuilder bldr = new HashCodeBuilder(17, 33); + return bldr.append(this.id) + .append(this.usageCount) + .append(this.creationDate.getTime()) + .append(this.authentication).toHashCode(); + } + } + + public static class MockAuthenticationHandler implements AuthenticationHandler { + + @Override + public DefaultHandlerResult authenticate(final Credential credential) throws GeneralSecurityException, PreventedException { + if (credential instanceof HttpBasedServiceCredential) { + return new DefaultHandlerResult(this, (HttpBasedServiceCredential) credential); + } else { + return new DefaultHandlerResult(this, new BasicCredentialMetaData(credential)); + } + } + + @Override + public boolean supports(final Credential credential) { + return true; + } + + @Override + public String getName() { + return "MockAuthenticationHandler"; + } + } +} diff --git a/cas-server-integration-memcached/src/test/resources/log4j2.xml b/cas-server-integration-memcached/src/test/resources/log4j2.xml new file mode 100644 index 000000000000..64bcbaf32d5e --- /dev/null +++ b/cas-server-integration-memcached/src/test/resources/log4j2.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-integration-memcached/src/test/resources/ticketRegistry-test.xml b/cas-server-integration-memcached/src/test/resources/ticketRegistry-test.xml new file mode 100644 index 000000000000..bbe5297fc00d --- /dev/null +++ b/cas-server-integration-memcached/src/test/resources/ticketRegistry-test.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-integration-restlet/NOTICE b/cas-server-integration-restlet/NOTICE new file mode 100644 index 000000000000..8f04a38121c2 --- /dev/null +++ b/cas-server-integration-restlet/NOTICE @@ -0,0 +1,106 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache Commons Logging under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS Restlet Integration - DEPRECATED under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + CDI APIs under Apache License, Version 2.0 + Code Generation Library under ASF 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + Restlet Core - API and Engine under Apache 2.0 license or LGPL 3.0 license or LGPL 2.1 license or CDDL 1.0 license or EPL 1.0 license + Restlet Extension - Servlet under Apache 2.0 license or LGPL 3.0 license or LGPL 2.1 license or CDDL 1.0 license or EPL 1.0 license + Restlet Extension - SLF4J under Apache 2.0 license or LGPL 3.0 license or LGPL 2.1 license or CDDL 1.0 license or EPL 1.0 license + Restlet Extension - Spring Framework under Apache 2.0 license or LGPL 3.0 license or LGPL 2.1 license or CDDL 1.0 license or EPL 1.0 license + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-integration-restlet/pom.xml b/cas-server-integration-restlet/pom.xml new file mode 100644 index 000000000000..b975d83790a5 --- /dev/null +++ b/cas-server-integration-restlet/pom.xml @@ -0,0 +1,101 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-integration-restlet + jar + Apereo CAS Restlet Integration - DEPRECATED + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + org.restlet.jee + org.restlet + ${restlet.version} + compile + + + + org.restlet.jee + org.restlet.ext.spring + ${restlet.version} + + + org.springframework + spring-asm + + + org.springframework + spring-web + + + org.springframework + spring-expression + + + runtime + + + + org.restlet.jee + org.restlet.ext.servlet + ${restlet.version} + runtime + + + + org.restlet.jee + org.restlet.ext.slf4j + ${restlet.version} + runtime + + + + org.springframework + spring-beans + compile + + + + + + restlet-repository + Restlet Repository + http://maven.restlet.org + + + + + 2.1.0 + ${project.parent.basedir} + + + diff --git a/cas-server-integration-restlet/src/main/java/org/jasig/cas/integration/restlet/TicketGrantingTicketResource.java b/cas-server-integration-restlet/src/main/java/org/jasig/cas/integration/restlet/TicketGrantingTicketResource.java new file mode 100644 index 000000000000..df3c95bd2ae9 --- /dev/null +++ b/cas-server-integration-restlet/src/main/java/org/jasig/cas/integration/restlet/TicketGrantingTicketResource.java @@ -0,0 +1,96 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.integration.restlet; + +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.ticket.InvalidTicketException; +import org.jasig.cas.ticket.ServiceTicket; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Status; +import org.restlet.representation.Representation; +import org.restlet.representation.Variant; +import org.restlet.resource.Delete; +import org.restlet.resource.Post; +import org.restlet.resource.ServerResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of a Restlet resource for creating Service Tickets from a + * {@link org.jasig.cas.ticket.TicketGrantingTicket}, as well as deleting a + * {@link org.jasig.cas.ticket.TicketGrantingTicket}. + * + * @author Scott Battaglia + * @since 3.3 + * @deprecated As of 4.1. Use the {@link TicketResource} implementation from cas-server-support-rest module + */ +@Deprecated +public final class TicketGrantingTicketResource extends ServerResource { + private static final Logger LOGGER = LoggerFactory.getLogger(TicketGrantingTicketResource.class); + + @Autowired + private CentralAuthenticationService centralAuthenticationService; + + private String ticketGrantingTicketId; + + @Override + public void init(final Context context, final Request request, final Response response) { + super.init(context, request, response); + this.ticketGrantingTicketId = (String) request.getAttributes().get("ticketGrantingTicketId"); + this.setNegotiated(false); + this.getVariants().add(new Variant(MediaType.APPLICATION_WWW_FORM)); + } + + /** + * Removes the TGT. + */ + @Delete + public void removeRepresentations() { + this.centralAuthenticationService.destroyTicketGrantingTicket(this.ticketGrantingTicketId); + getResponse().setStatus(Status.SUCCESS_OK); + } + + /** + * Accept service and attempt to grant service ticket. + * + * @param entity the entity + */ + @Post + public void acceptRepresentation(final Representation entity) { + final Form form = new Form(entity); + final String serviceUrl = form.getFirstValue("service"); + try { + final ServiceTicket serviceTicketId = this.centralAuthenticationService.grantServiceTicket( + this.ticketGrantingTicketId, + new SimpleWebApplicationServiceImpl(serviceUrl)); + getResponse().setEntity(serviceTicketId.getId(), MediaType.TEXT_PLAIN); + } catch (final InvalidTicketException e) { + getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND, "TicketGrantingTicket could not be found."); + } catch (final Exception e) { + LOGGER.error(e.getMessage(), e); + getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, e.getMessage()); + } + } +} diff --git a/cas-server-integration-restlet/src/main/java/org/jasig/cas/integration/restlet/TicketResource.java b/cas-server-integration-restlet/src/main/java/org/jasig/cas/integration/restlet/TicketResource.java new file mode 100644 index 000000000000..fde8a8dfcd9b --- /dev/null +++ b/cas-server-integration-restlet/src/main/java/org/jasig/cas/integration/restlet/TicketResource.java @@ -0,0 +1,301 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.integration.restlet; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.restlet.Request; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Reference; +import org.restlet.data.Status; +import org.restlet.representation.Representation; +import org.restlet.resource.Post; +import org.restlet.resource.ServerResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.web.bind.support.WebRequestDataBinder; +import org.springframework.web.context.request.WebRequest; + +import java.security.Principal; +import java.util.Formatter; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * Handles the creation of Ticket Granting Tickets. + * + * @author Scott Battaglia + * @since 3.3 + * @deprecated Use TicketsResource implementation from cas-server-support-rest module + */ +@Deprecated +public class TicketResource extends ServerResource { + + private static final Logger LOGGER = LoggerFactory.getLogger(TicketResource.class); + + @Autowired + private CentralAuthenticationService centralAuthenticationService; + + /** + * Instantiates a new ticket resource. + */ + public TicketResource() { + setNegotiated(false); + } + + /** + * Accept credentials and attempt to create the TGT. + * + * @param entity the entity + */ + @Post + public final void acceptRepresentation(final Representation entity) { + LOGGER.debug("Obtaining credentials..."); + final Credential c = obtainCredentials(); + + if (c == null) { + LOGGER.trace("No credentials could be obtained from the REST entity representation"); + getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "Invalid or missing credentials"); + return; + } + + try (final Formatter fmt = new Formatter()) { + final TicketGrantingTicket ticketGrantingTicketId = this.centralAuthenticationService.createTicketGrantingTicket(c); + getResponse().setStatus(determineStatus()); + final Reference ticketReference = getRequest().getResourceRef().addSegment(ticketGrantingTicketId.getId()); + getResponse().setLocationRef(ticketReference); + + fmt.format(""); + fmt.format("%s %s", getResponse().getStatus().getCode(), getResponse().getStatus().getDescription()) + .format("

TGT Created

Service:") + .format("
"); + + getResponse().setEntity(fmt.toString(), MediaType.TEXT_HTML); + } catch (final Exception e) { + LOGGER.error(e.getMessage(), e); + getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, e.getMessage()); + } + } + /** + * Template method for determining which status to return on a successful ticket creation. + * This method exists for compatibility reasons with bad clients (i.e. Flash) that can't + * process 201 with a Location header. + * + * @return the status to return. + */ + protected Status determineStatus() { + return Status.SUCCESS_CREATED; + } + + /** + * Obtain credentials from the request. + * + * @return the credential + */ + protected Credential obtainCredentials() { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + final WebRequestDataBinder binder = new WebRequestDataBinder(c); + final RestletWebRequest webRequest = new RestletWebRequest(getRequest()); + + final Form form = new Form(getRequest().getEntity()); + logFormRequest(form); + + if (!form.isEmpty()) { + binder.bind(webRequest); + return c; + } + LOGGER.trace("Failed to bind the request to credentials. Resulting form is empty"); + return null; + } + + /** + * Log the form request. + * + * @param form the form + */ + private void logFormRequest(final Form form) { + if (LOGGER.isDebugEnabled()) { + final Set pairs = new HashSet<>(); + for (final String name : form.getNames()) { + final StringBuilder builder = new StringBuilder(); + builder.append(name); + builder.append(": "); + if (!"password".equalsIgnoreCase(name)) { + builder.append(form.getValues(name)); + } else { + builder.append("*****"); + } + pairs.add(builder.toString()); + } + LOGGER.debug(StringUtils.join(pairs, ", ")); + } + } + + protected static class RestletWebRequest implements WebRequest { + private final Form form; + private final Request request; + + /** + * Instantiates a new restlet web request. + * + * @param request the request + */ + public RestletWebRequest(final Request request) { + this.form = new Form(request.getEntity()); + this.request = request; + } + + @Override + public boolean checkNotModified(final String s) { + return false; + } + + @Override + public boolean checkNotModified(final long lastModifiedTimestamp) { + return false; + } + + @Override + public String getContextPath() { + return this.request.getResourceRef().getPath(); + } + + @Override + public String getDescription(final boolean includeClientInfo) { + return null; + } + + @Override + public Locale getLocale() { + return LocaleContextHolder.getLocale(); + } + + @Override + public String getParameter(final String paramName) { + return this.form.getFirstValue(paramName); + } + + @Override + public Map getParameterMap() { + final Map conversion = new HashMap<>(); + + for (final Map.Entry entry : this.form.getValuesMap().entrySet()) { + conversion.put(entry.getKey(), new String[] {entry.getValue()}); + } + + return conversion; + } + + @Override + public String[] getParameterValues(final String paramName) { + return this.form.getValuesArray(paramName); + } + + @Override + public String getRemoteUser() { + return null; + } + + @Override + public Principal getUserPrincipal() { + return null; + } + + @Override + public boolean isSecure() { + return this.request.isConfidential(); + } + + @Override + public boolean isUserInRole(final String role) { + return false; + } + + @Override + public Object getAttribute(final String name, final int scope) { + return null; + } + + @Override + public String[] getAttributeNames(final int scope) { + return null; + } + + @Override + public String getSessionId() { + return null; + } + + @Override + public Object getSessionMutex() { + return null; + } + + @Override + public void registerDestructionCallback(final String name, final Runnable callback, final int scope) { + // nothing to do + } + + @Override + public void removeAttribute(final String name, final int scope) { + // nothing to do + } + + @Override + public void setAttribute(final String name, final Object value, final int scope) { + // nothing to do + } + + @Override + public String getHeader(final String s) { + return null; + } + + @Override + public String[] getHeaderValues(final String s) { + return new String[0]; + } + + @Override + public Iterator getHeaderNames() { + return null; + } + + @Override + public Iterator getParameterNames() { + return null; + } + + @Override + public Object resolveReference(final String s) { + return null; + } + } +} diff --git a/cas-server-integration-restlet/src/site/site.xml b/cas-server-integration-restlet/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-integration-restlet/src/site/site.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/cas-server-3.4.2/cas-server-integration-jboss/.cvsignore b/cas-server-support-generic/.cvsignore similarity index 100% rename from cas-server-3.4.2/cas-server-integration-jboss/.cvsignore rename to cas-server-support-generic/.cvsignore diff --git a/cas-server-support-generic/NOTICE b/cas-server-support-generic/NOTICE new file mode 100644 index 000000000000..2af1aa05f8d9 --- /dev/null +++ b/cas-server-support-generic/NOTICE @@ -0,0 +1,101 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS Generic Support under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + CDI APIs under Apache License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-support-generic/pom.xml b/cas-server-support-generic/pom.xml new file mode 100644 index 000000000000..b3908c9235ea --- /dev/null +++ b/cas-server-support-generic/pom.xml @@ -0,0 +1,43 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-support-generic + jar + Apereo CAS Generic Support + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + + ${project.parent.basedir} + + diff --git a/cas-server-support-generic/src/main/java/org/jasig/cas/adaptors/generic/FileAuthenticationHandler.java b/cas-server-support-generic/src/main/java/org/jasig/cas/adaptors/generic/FileAuthenticationHandler.java new file mode 100644 index 000000000000..7c192f285526 --- /dev/null +++ b/cas-server-support-generic/src/main/java/org/jasig/cas/adaptors/generic/FileAuthenticationHandler.java @@ -0,0 +1,122 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.generic; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler; +import org.springframework.core.io.Resource; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.auth.login.FailedLoginException; +import javax.validation.constraints.NotNull; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.security.GeneralSecurityException; + +/** + * Class designed to read data from a file in the format of USERNAME SEPARATOR + * PASSWORD that will go line by line and look for the username. If it finds the + * username it will compare the supplied password (first put through a + * PasswordTranslator) that is compared to the password provided in the file. If + * there is a match, the user is authenticated. Note that the default password + * translator is a plaintext password translator and the default separator is + * "::" (without quotes). + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.0.0 + */ +public class FileAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler { + + /** The default separator in the file. */ + private static final String DEFAULT_SEPARATOR = "::"; + + /** The separator to use. */ + @NotNull + private String separator = DEFAULT_SEPARATOR; + + /** The filename to read the list of usernames from. */ + @NotNull + private Resource fileName; + + /** + * {@inheritDoc} + */ + @Override + protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential) + throws GeneralSecurityException, PreventedException { + try { + final String username = credential.getUsername(); + final String passwordOnRecord = getPasswordOnRecord(username); + if (StringUtils.isBlank(passwordOnRecord)) { + throw new AccountNotFoundException(username + " not found in backing file."); + } + final String password = credential.getPassword(); + if (StringUtils.isNotBlank(password) && this.getPasswordEncoder().encode(password).equals(passwordOnRecord)) { + return createHandlerResult(credential, this.principalFactory.createPrincipal(username), null); + } + } catch (final IOException e) { + throw new PreventedException("IO error reading backing file", e); + } + throw new FailedLoginException(); + } + + /** + * @param fileName The fileName to set. + */ + public final void setFileName(final Resource fileName) { + this.fileName = fileName; + } + + /** + * @param separator The separator to set. + */ + public final void setSeparator(final String separator) { + this.separator = separator; + } + + /** + * Gets the password on record. + * + * @param username the username + * @return the password on record + * @throws IOException Signals that an I/O exception has occurred. + */ + private String getPasswordOnRecord(final String username) throws IOException { + try (BufferedReader bufferedReader = + new BufferedReader( + new InputStreamReader(this.fileName.getInputStream(), Charset.defaultCharset()))) { + String line = bufferedReader.readLine(); + while (line != null) { + final String[] lineFields = line.split(this.separator); + final String userOnRecord = lineFields[0]; + final String passOnRecord = lineFields[1]; + if (username.equals(userOnRecord)) { + return passOnRecord; + } + line = bufferedReader.readLine(); + } + } + return null; + } +} diff --git a/cas-server-support-generic/src/main/java/org/jasig/cas/adaptors/generic/RejectUsersAuthenticationHandler.java b/cas-server-support-generic/src/main/java/org/jasig/cas/adaptors/generic/RejectUsersAuthenticationHandler.java new file mode 100644 index 000000000000..f07c49562b62 --- /dev/null +++ b/cas-server-support-generic/src/main/java/org/jasig/cas/adaptors/generic/RejectUsersAuthenticationHandler.java @@ -0,0 +1,83 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.generic; + +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler; + +import javax.security.auth.login.FailedLoginException; +import javax.validation.constraints.NotNull; +import java.security.GeneralSecurityException; +import java.util.List; +import java.util.Set; + +/** + * AuthenticationHandler which fails to authenticate a user purporting to be one + * of the blocked usernames, and blindly authenticates all other users. + *

+ * Note that RejectUsersAuthenticationHandler throws an exception when the user + * is found in the map. This is done to indicate that this is an extreme case + * and any AuthenticationManager checking the RejectUsersAuthenticationHandler + * should not continue checking other Authentication Handlers on the failure of + * RejectUsersAuthenticationHandler to authenticate someone. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public class RejectUsersAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler { + + /** The collection of users to reject. */ + @NotNull + private Set users; + + @Override + protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential) + throws GeneralSecurityException, PreventedException { + + final String username = credential.getUsername(); + if (this.users.contains(username)) { + throw new FailedLoginException(); + } + + return createHandlerResult(credential, this.principalFactory.createPrincipal(username), null); + } + + /** + * @deprecated As of 4.1. Use {@link #setUsers(Set)} instead. + * Set the Collection of usernames which we will fail to authenticate. + * + * @param users The Collection of usernames we should not authenticate. + */ + @Deprecated + public final void setUsers(final List users) { + logger.warn("setUsers(List) is deprecated and has no effect. Consider defining a set instead"); + } + + /** + * Set the Collection of usernames which we will fail to authenticate. + * + * @param users The Collection of usernames we should not authenticate. + */ + public final void setUsers(final Set users) { + this.users = users; + } + +} diff --git a/cas-server-support-generic/src/main/java/org/jasig/cas/adaptors/generic/remote/RemoteAddressAuthenticationHandler.java b/cas-server-support-generic/src/main/java/org/jasig/cas/adaptors/generic/remote/RemoteAddressAuthenticationHandler.java new file mode 100644 index 000000000000..3c34cbcee813 --- /dev/null +++ b/cas-server-support-generic/src/main/java/org/jasig/cas/adaptors/generic/remote/RemoteAddressAuthenticationHandler.java @@ -0,0 +1,145 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.generic.remote; + +import org.jasig.cas.authentication.AbstractAuthenticationHandler; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.HandlerResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.security.auth.login.FailedLoginException; +import javax.validation.constraints.NotNull; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.GeneralSecurityException; + +/** + * Checks if the remote address is in the range of allowed addresses. + * + * @author David Harrison + * @author Scott Battaglia + * @since 3.2.1 + * + */ +public final class RemoteAddressAuthenticationHandler extends AbstractAuthenticationHandler { + + private static final int HEX_RIGHT_SHIFT_COEFFICIENT = 0xff; + private final Logger logger = LoggerFactory.getLogger(getClass()); + + /** The network netmask. */ + @NotNull + private InetAddress inetNetmask; + + /** The network base address. */ + @NotNull + private InetAddress inetNetwork; + + @Override + public HandlerResult authenticate(final Credential credential) throws GeneralSecurityException { + final RemoteAddressCredential c = (RemoteAddressCredential) credential; + try { + final InetAddress inetAddress = InetAddress.getByName(c.getRemoteAddress().trim()); + if (containsAddress(this.inetNetwork, this.inetNetmask, inetAddress)) { + return new DefaultHandlerResult(this, c, this.principalFactory.createPrincipal(c.getId())); + } + } catch (final UnknownHostException e) { + logger.debug("Unknown host {}", c.getRemoteAddress()); + } + throw new FailedLoginException(c.getRemoteAddress() + " not in allowed range."); + } + + @Override + public boolean supports(final Credential credential) { + return credential instanceof RemoteAddressCredential; + } + + /** + * The following code is from the Apache Software Foundations's Lenya project + * See InetAddressUtil.java + * Distributed under the Apache 2.0 software license + */ + + /** + * Checks if a subnet contains a specific IP address. + * + * @param network The network address. + * @param netmask The subnet mask. + * @param ip The IP address to check. + * @return A boolean value. + */ + private boolean containsAddress(final InetAddress network, final InetAddress netmask, final InetAddress ip) { + logger.debug("Checking IP address: {} in {} by {}", ip, network, netmask); + + final byte[] networkBytes = network.getAddress(); + final byte[] netmaskBytes = netmask.getAddress(); + final byte[] ipBytes = ip.getAddress(); + + /* check IPv4/v6-compatibility or parameters: */ + if(networkBytes.length != netmaskBytes.length + || netmaskBytes.length != ipBytes.length) { + logger.debug("Network address {}, subnet mask {} and/or host address {}" + + " have different sizes! (return false ...)", network, netmask, ip); + return false; + } + + /* Check if the masked network and ip addresses match: */ + for(int i=0; i < netmaskBytes.length; i++) { + final int mask = netmaskBytes[i] & HEX_RIGHT_SHIFT_COEFFICIENT; + if((networkBytes[i] & mask) != (ipBytes[i] & mask)) { + logger.debug("{} is not in {}/{}", ip, network, netmask); + return false; + } + } + logger.debug("{} is in {}/{}", ip, network, netmask); + return true; + } + + /** + * @param ipAddressRange the IP address range that should be allowed trusted logins * + */ + public void setIpNetworkRange(final String ipAddressRange) { + + if(ipAddressRange != null) { + + final String[] splitAddress = ipAddressRange.split("/"); + + if (splitAddress.length == 2) { + // A valid ip address/netmask was supplied parse values + final String network = splitAddress[0].trim(); + final String netmask = splitAddress[1].trim(); + + try { + this.inetNetwork = InetAddress.getByName(network); + logger.debug("InetAddress network: {}", this.inetNetwork.toString()); + } catch (final UnknownHostException e) { + logger.error("The network address was not valid: {}", e.getMessage()); + } + + try { + this.inetNetmask = InetAddress.getByName(netmask); + logger.debug("InetAddress netmask: {}", this.inetNetmask.toString()); + } catch (final UnknownHostException e) { + logger.error("The network netmask was not valid: {}", e.getMessage()); + } + } + } + } +} diff --git a/cas-server-support-generic/src/main/java/org/jasig/cas/adaptors/generic/remote/RemoteAddressCredential.java b/cas-server-support-generic/src/main/java/org/jasig/cas/adaptors/generic/remote/RemoteAddressCredential.java new file mode 100644 index 000000000000..2c0e63bf1dbb --- /dev/null +++ b/cas-server-support-generic/src/main/java/org/jasig/cas/adaptors/generic/remote/RemoteAddressCredential.java @@ -0,0 +1,53 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.generic.remote; + +import org.jasig.cas.authentication.AbstractCredential; + +/** + * + * @author Scott Battaglia + * @since 3.2.1 + * + */ +public final class RemoteAddressCredential extends AbstractCredential { + + /** Serialization version marker. */ + private static final long serialVersionUID = -3638145328441211073L; + + private final String remoteAddress; + + /** + * Instantiates a new remote address credential. + * + * @param remoteAddress the remote address + */ + public RemoteAddressCredential(final String remoteAddress) { + this.remoteAddress = remoteAddress; + } + + public String getRemoteAddress() { + return this.remoteAddress; + } + + @Override + public String getId() { + return this.remoteAddress; + } +} diff --git a/cas-server-support-generic/src/main/java/org/jasig/cas/adaptors/generic/remote/RemoteAddressNonInteractiveCredentialsAction.java b/cas-server-support-generic/src/main/java/org/jasig/cas/adaptors/generic/remote/RemoteAddressNonInteractiveCredentialsAction.java new file mode 100644 index 000000000000..4c2d01595719 --- /dev/null +++ b/cas-server-support-generic/src/main/java/org/jasig/cas/adaptors/generic/remote/RemoteAddressNonInteractiveCredentialsAction.java @@ -0,0 +1,47 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.generic.remote; + +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.web.flow.AbstractNonInteractiveCredentialsAction; +import org.jasig.cas.web.support.WebUtils; +import org.springframework.util.StringUtils; +import org.springframework.webflow.execution.RequestContext; + +import javax.servlet.http.HttpServletRequest; + +/** + * + * @author Scott Battaglia + * @since 3.2.1 + */ +public final class RemoteAddressNonInteractiveCredentialsAction extends AbstractNonInteractiveCredentialsAction { + @Override + protected Credential constructCredentialsFromRequest(final RequestContext context) { + final HttpServletRequest request = WebUtils.getHttpServletRequest(context); + final String remoteAddress = request.getRemoteAddr(); + + if (StringUtils.hasText(remoteAddress)) { + return new RemoteAddressCredential(remoteAddress); + } + + logger.debug("No remote address found."); + return null; + } +} diff --git a/cas-server-support-generic/src/site/site.xml b/cas-server-support-generic/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-support-generic/src/site/site.xml @@ -0,0 +1,28 @@ + + + + +

+ + + + diff --git a/cas-server-3.4.2/cas-server-integration-jboss/src/test/clover/clover.license b/cas-server-support-generic/src/test/clover/clover.license similarity index 100% rename from cas-server-3.4.2/cas-server-integration-jboss/src/test/clover/clover.license rename to cas-server-support-generic/src/test/clover/clover.license diff --git a/cas-server-support-generic/src/test/java/org/jasig/cas/adaptors/generic/FileAuthenticationHandlerTests.java b/cas-server-support-generic/src/test/java/org/jasig/cas/adaptors/generic/FileAuthenticationHandlerTests.java new file mode 100644 index 000000000000..743403048d8e --- /dev/null +++ b/cas-server-support-generic/src/test/java/org/jasig/cas/adaptors/generic/FileAuthenticationHandlerTests.java @@ -0,0 +1,172 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.generic; + +import org.jasig.cas.authentication.HttpBasedServiceCredential; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.services.RegisteredServiceImpl; +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; + +import javax.security.auth.login.AccountNotFoundException; +import javax.security.auth.login.FailedLoginException; +import java.net.MalformedURLException; +import java.net.URL; + +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class FileAuthenticationHandlerTests { + + private FileAuthenticationHandler authenticationHandler; + + @Before + public void setUp() throws Exception { + this.authenticationHandler = new FileAuthenticationHandler(); + this.authenticationHandler.setFileName( + new ClassPathResource("org/jasig/cas/adaptors/generic/authentication.txt")); + + } + + @Test + public void verifySupportsProperUserCredentials() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + c.setUsername("scott"); + c.setPassword("rutgers"); + assertNotNull(this.authenticationHandler.authenticate(c)); + } + + @Test + public void verifyDoesntSupportBadUserCredentials() { + try { + final RegisteredServiceImpl svc = new RegisteredServiceImpl(); + svc.setServiceId("https://some.app.edu"); + final HttpBasedServiceCredential c = new HttpBasedServiceCredential( + new URL("http://www.rutgers.edu"), svc); + assertFalse(this.authenticationHandler.supports(c)); + } catch (final MalformedURLException e) { + fail("MalformedURLException caught."); + } + } + + @Test + public void verifyAuthenticatesUserInFileWithDefaultSeparator() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + c.setUsername("scott"); + c.setPassword("rutgers"); + + assertNotNull(this.authenticationHandler.authenticate(c)); + } + + @Test(expected = AccountNotFoundException.class) + public void verifyFailsUserNotInFileWithDefaultSeparator() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + c.setUsername("fds"); + c.setPassword("rutgers"); + this.authenticationHandler.authenticate(c); + } + + @Test(expected = AccountNotFoundException.class) + public void verifyFailsNullUserName() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + c.setUsername(null); + c.setPassword("user"); + this.authenticationHandler.authenticate(c); + } + + @Test(expected = AccountNotFoundException.class) + public void verifyFailsNullUserNameAndPassword() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + c.setUsername(null); + c.setPassword(null); + this.authenticationHandler.authenticate(c); + } + + @Test(expected = FailedLoginException.class) + public void verifyFailsNullPassword() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + c.setUsername("scott"); + c.setPassword(null); + + this.authenticationHandler.authenticate(c); + } + + @Test + public void verifyAuthenticatesUserInFileWithCommaSeparator() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + this.authenticationHandler.setFileName( + new ClassPathResource("org/jasig/cas/adaptors/generic/authentication2.txt")); + this.authenticationHandler.setSeparator(","); + + c.setUsername("scott"); + c.setPassword("rutgers"); + + assertNotNull(this.authenticationHandler.authenticate(c)); + } + + @Test(expected = AccountNotFoundException.class) + public void verifyFailsUserNotInFileWithCommaSeparator() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + this.authenticationHandler.setFileName( + new ClassPathResource("org/jasig/cas/adaptors/generic/authentication2.txt")); + this.authenticationHandler.setSeparator(","); + + c.setUsername("fds"); + c.setPassword("rutgers"); + this.authenticationHandler.authenticate(c); + } + + @Test(expected = FailedLoginException.class) + public void verifyFailsGoodUsernameBadPassword() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + this.authenticationHandler.setFileName( + new ClassPathResource("org/jasig/cas/adaptors/generic/authentication2.txt")); + this.authenticationHandler.setSeparator(","); + + c.setUsername("scott"); + c.setPassword("rutgers1"); + + this.authenticationHandler.authenticate(c); + } + + @Test(expected = PreventedException.class) + public void verifyAuthenticateNoFileName() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + this.authenticationHandler.setFileName(new ClassPathResource("fff")); + + c.setUsername("scott"); + c.setPassword("rutgers"); + + this.authenticationHandler.authenticate(c); + } +} diff --git a/cas-server-support-generic/src/test/java/org/jasig/cas/adaptors/generic/RejectUsersAuthenticationHandlerTests.java b/cas-server-support-generic/src/test/java/org/jasig/cas/adaptors/generic/RejectUsersAuthenticationHandlerTests.java new file mode 100644 index 000000000000..6390902a93af --- /dev/null +++ b/cas-server-support-generic/src/test/java/org/jasig/cas/adaptors/generic/RejectUsersAuthenticationHandlerTests.java @@ -0,0 +1,112 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.generic; + +import org.jasig.cas.authentication.HttpBasedServiceCredential; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.services.RegisteredServiceImpl; +import org.junit.Test; + +import javax.security.auth.login.AccountNotFoundException; +import javax.security.auth.login.FailedLoginException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class RejectUsersAuthenticationHandlerTests { + + private final Set users; + + private final RejectUsersAuthenticationHandler authenticationHandler; + + public RejectUsersAuthenticationHandlerTests() throws Exception { + this.users = new HashSet<>(); + + this.users.add("scott"); + this.users.add("dima"); + this.users.add("bill"); + + this.authenticationHandler = new RejectUsersAuthenticationHandler(); + + this.authenticationHandler.setUsers(this.users); + } + + @Test + public void verifySupportsProperUserCredentials() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + c.setUsername("fff"); + c.setPassword("rutgers"); + assertNotNull(this.authenticationHandler.authenticate(c)); + } + + @Test + public void verifyDoesntSupportBadUserCredentials() { + try { + final RegisteredServiceImpl svc = new RegisteredServiceImpl(); + svc.setServiceId("https://some.app.edu"); + assertFalse(this.authenticationHandler + .supports(new HttpBasedServiceCredential(new URL( + "http://www.rutgers.edu"), svc))); + } catch (final MalformedURLException e) { + fail("Could not resolve URL."); + } + } + + @Test(expected=FailedLoginException.class) + public void verifyFailsUserInMap() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + c.setUsername("scott"); + c.setPassword("rutgers"); + this.authenticationHandler.authenticate(c); + } + + @Test + public void verifyPassesUserNotInMap() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + c.setUsername("fds"); + c.setPassword("rutgers"); + + assertNotNull(this.authenticationHandler.authenticate(c)); + } + + @Test(expected = AccountNotFoundException.class) + public void verifyPassesNullUserName() throws Exception { + final UsernamePasswordCredential c = new UsernamePasswordCredential(); + + c.setUsername(null); + c.setPassword("user"); + + this.authenticationHandler.authenticate(c); + } + + @Test(expected = AccountNotFoundException.class) + public void verifyPassesNullUserNameAndPassword() throws Exception { + this.authenticationHandler.authenticate(new UsernamePasswordCredential()); + } +} diff --git a/cas-server-3.4.2/cas-server-support-generic/src/test/resources/org/jasig/cas/adaptors/generic/authentication.txt b/cas-server-support-generic/src/test/resources/org/jasig/cas/adaptors/generic/authentication.txt similarity index 100% rename from cas-server-3.4.2/cas-server-support-generic/src/test/resources/org/jasig/cas/adaptors/generic/authentication.txt rename to cas-server-support-generic/src/test/resources/org/jasig/cas/adaptors/generic/authentication.txt diff --git a/cas-server-support-generic/src/test/resources/org/jasig/cas/adaptors/generic/authentication2.txt b/cas-server-support-generic/src/test/resources/org/jasig/cas/adaptors/generic/authentication2.txt new file mode 100644 index 000000000000..7da8d43df0f0 --- /dev/null +++ b/cas-server-support-generic/src/test/resources/org/jasig/cas/adaptors/generic/authentication2.txt @@ -0,0 +1,3 @@ +scott,rutgers +bart,hello +bill,YES diff --git a/cas-server-3.4.2/cas-server-support-generic/.cvsignore b/cas-server-support-jdbc/.cvsignore similarity index 100% rename from cas-server-3.4.2/cas-server-support-generic/.cvsignore rename to cas-server-support-jdbc/.cvsignore diff --git a/cas-server-support-jdbc/NOTICE b/cas-server-support-jdbc/NOTICE new file mode 100644 index 000000000000..77a429407614 --- /dev/null +++ b/cas-server-support-jdbc/NOTICE @@ -0,0 +1,108 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apache Shiro :: Core under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS JDBC Support under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + CDI APIs under Apache License, Version 2.0 + ClassMate under The Apache Software License, Version 2.0 + Commons BeanUtils under The Apache Software License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Hibernate JPA Support under GNU Lesser General Public License + Hibernate Validator Engine under Apache License, Version 2.0 + HyperSQL Database under HSQLDB License, a BSD open source license + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + JBoss Logging I18n Annotations under Public Domain + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-support-jdbc/pom.xml b/cas-server-support-jdbc/pom.xml new file mode 100644 index 000000000000..f0c73859acaf --- /dev/null +++ b/cas-server-support-jdbc/pom.xml @@ -0,0 +1,112 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-support-jdbc + jar + Apereo CAS JDBC Support + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + org.apache.commons + commons-lang3 + compile + + + + org.springframework + spring-jdbc + compile + + + + org.springframework + spring-orm + compile + + + + org.hibernate + hibernate-core + compile + + + + org.apache.shiro + shiro-core + compile + + + + + org.springframework + spring-context + test + + + + org.hibernate + hibernate-entitymanager + test + + + + org.hibernate + hibernate-validator + test + + + + org.hsqldb + hsqldb + test + + + + org.springframework.webflow + spring-webflow + test + + + + org.jasig.cas + cas-server-core + ${project.version} + test-jar + test + + + + + ${project.parent.basedir} + + diff --git a/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/AbstractJdbcUsernamePasswordAuthenticationHandler.java b/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/AbstractJdbcUsernamePasswordAuthenticationHandler.java new file mode 100644 index 000000000000..841d03bc5fef --- /dev/null +++ b/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/AbstractJdbcUsernamePasswordAuthenticationHandler.java @@ -0,0 +1,64 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.jdbc; + +import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler; +import org.springframework.jdbc.core.JdbcTemplate; + +import javax.sql.DataSource; +import javax.validation.constraints.NotNull; + +/** + * Abstract class for database authentication handlers. + * + * @author Scott Battaglia + * @since 3.0.0.3 + */ +public abstract class AbstractJdbcUsernamePasswordAuthenticationHandler extends + AbstractUsernamePasswordAuthenticationHandler { + + @NotNull + private JdbcTemplate jdbcTemplate; + + @NotNull + private DataSource dataSource; + + /** + * Method to set the datasource and generate a JdbcTemplate. + * + * @param dataSource the datasource to use. + */ + public final void setDataSource(final DataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + this.dataSource = dataSource; + } + + /** + * Method to return the jdbcTemplate. + * + * @return a fully created JdbcTemplate. + */ + protected final JdbcTemplate getJdbcTemplate() { + return this.jdbcTemplate; + } + + protected final DataSource getDataSource() { + return this.dataSource; + } +} diff --git a/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/BindModeSearchDatabaseAuthenticationHandler.java b/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/BindModeSearchDatabaseAuthenticationHandler.java new file mode 100644 index 000000000000..3dcb3f3b7ef4 --- /dev/null +++ b/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/BindModeSearchDatabaseAuthenticationHandler.java @@ -0,0 +1,64 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.jdbc; + +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.springframework.jdbc.datasource.DataSourceUtils; + +import javax.security.auth.login.FailedLoginException; +import java.security.GeneralSecurityException; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * This class attempts to authenticate the user by opening a connection to the + * database with the provided username and password. Servers are provided as a + * Properties class with the key being the URL and the property being the type + * of database driver needed. + * + * @author Scott Battaglia + * @author Dmitriy Kopylenko + * @author Marvin S. Addison + * + * @since 3.0.0 + */ +public class BindModeSearchDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler { + + /** + * {@inheritDoc} + */ + @Override + protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential) + throws GeneralSecurityException, PreventedException { + + try { + final String username = credential.getUsername(); + final String password = getPasswordEncoder().encode(credential.getPassword()); + final Connection c = this.getDataSource().getConnection(username, password); + DataSourceUtils.releaseConnection(c, this.getDataSource()); + return createHandlerResult(credential, this.principalFactory.createPrincipal(username), null); + } catch (final SQLException e) { + throw new FailedLoginException(e.getMessage()); + } catch (final Exception e) { + throw new PreventedException("Unexpected SQL connection error", e); + } + } +} diff --git a/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java b/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java new file mode 100644 index 000000000000..1aad3b2d0f36 --- /dev/null +++ b/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java @@ -0,0 +1,240 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.jdbc; + +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.crypto.hash.ConfigurableHashService; +import org.apache.shiro.crypto.hash.DefaultHashService; +import org.apache.shiro.crypto.hash.HashRequest; +import org.apache.shiro.util.ByteSource; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.IncorrectResultSizeDataAccessException; + +import javax.security.auth.login.AccountNotFoundException; +import javax.security.auth.login.FailedLoginException; +import javax.sql.DataSource; +import javax.validation.constraints.NotNull; +import java.security.GeneralSecurityException; +import java.util.Map; + +/** + * A JDBC querying handler that will pull back the password and + * the private salt value for a user and validate the encoded + * password using the public salt value. Assumes everything + * is inside the same database table. Supports settings for + * number of iterations as well as private salt. + * + *

+ * This handler uses the hashing method defined by Apache Shiro's + * {@link org.apache.shiro.crypto.hash.DefaultHashService}. Refer to the Javadocs + * to learn more about the behavior. If the hashing behavior and/or configuration + * of private and public salts does nto meet your needs, a extension can be developed + * to specify alternative methods of encoding and digestion of the encoded password. + *

+ * @author Misagh Moayyed + * @author Charles Hasegawa (mailto:chasegawa@unicon.net) + * @since 4.1.0 + */ +public class QueryAndEncodeDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler { + + private static final String DEFAULT_PASSWORD_FIELD = "password"; + private static final String DEFAULT_SALT_FIELD = "salt"; + private static final String DEFAULT_NUM_ITERATIONS_FIELD = "numIterations"; + + /** + * The Algorithm name. + */ + @NotNull + protected final String algorithmName; + + /** + * The Sql statement to execute. + */ + @NotNull + protected final String sql; + + /** + * The Password field name. + */ + @NotNull + protected String passwordFieldName = DEFAULT_PASSWORD_FIELD; + + /** + * The Salt field name. + */ + @NotNull + protected String saltFieldName = DEFAULT_SALT_FIELD; + + /** + * The Number of iterations field name. + */ + @NotNull + protected String numberOfIterationsFieldName = DEFAULT_NUM_ITERATIONS_FIELD; + + /** + * The number of iterations. Defaults to 0. + */ + protected long numberOfIterations; + + /** + * The static/private salt. + */ + protected String staticSalt; + + /** + * Instantiates a new Query and encode database authentication handler. + * + * @param datasource The database datasource + * @param sql the sql query to execute which must include a parameter placeholder + * for the user id. (i.e. SELECT * FROM table WHERE username = ? + * @param algorithmName the algorithm name (i.e. MessageDigestAlgorithms.SHA_512) + */ + + public QueryAndEncodeDatabaseAuthenticationHandler(final DataSource datasource, + final String sql, + final String algorithmName) { + super(); + setDataSource(datasource); + this.sql = sql; + this.algorithmName = algorithmName; + } + + @Override + protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential transformedCredential) + throws GeneralSecurityException, PreventedException { + final String username = getPrincipalNameTransformer().transform(transformedCredential.getUsername()); + final String encodedPsw = this.getPasswordEncoder().encode(transformedCredential.getPassword()); + + try { + final Map values = getJdbcTemplate().queryForMap(this.sql, username); + final String digestedPassword = digestEncodedPassword(encodedPsw, values); + + if (!values.get(this.passwordFieldName).equals(digestedPassword)) { + throw new FailedLoginException("Password does not match value on record."); + } + return createHandlerResult(transformedCredential, + this.principalFactory.createPrincipal(username), null); + + } catch (final IncorrectResultSizeDataAccessException e) { + if (e.getActualSize() == 0) { + throw new AccountNotFoundException(username + " not found with SQL query"); + } else { + throw new FailedLoginException("Multiple records found for " + username); + } + } catch (final DataAccessException e) { + throw new PreventedException("SQL exception while executing query for " + username, e); + } + + } + + /** + * Digest encoded password. + * + * @param encodedPassword the encoded password + * @param values the values retrieved from database + * @return the digested password + */ + protected String digestEncodedPassword(final String encodedPassword, final Map values) { + final ConfigurableHashService hashService = new DefaultHashService(); + + if (StringUtils.isNotBlank(this.staticSalt)) { + hashService.setPrivateSalt(ByteSource.Util.bytes(this.staticSalt)); + } + hashService.setHashAlgorithmName(this.algorithmName); + + Long numOfIterations = this.numberOfIterations; + if (values.containsKey(this.numberOfIterationsFieldName)) { + final String longAsStr = values.get(this.numberOfIterationsFieldName).toString(); + numOfIterations = Long.valueOf(longAsStr); + } + + hashService.setHashIterations(numOfIterations.intValue()); + if (!values.containsKey(this.saltFieldName)) { + throw new RuntimeException("Specified field name for salt does not exist in the results"); + } + + final String dynaSalt = values.get(this.saltFieldName).toString(); + final HashRequest request = new HashRequest.Builder() + .setSalt(dynaSalt) + .setSource(encodedPassword) + .build(); + return hashService.computeHash(request).toHex(); + } + + /** + * Sets static/private salt to be combined with the dynamic salt retrieved + * from the database. Optional. + * + *

If using this implementation as part of a password hashing strategy, + * it might be desirable to configure a private salt. + * A hash and the salt used to compute it are often stored together. + * If an attacker is ever able to access the hash (e.g. during password cracking) + * and it has the full salt value, the attacker has all of the input necessary + * to try to brute-force crack the hash (source + complete salt).

+ * + *

However, if part of the salt is not available to the attacker (because it is not stored with the hash), + * it is much harder to crack the hash value since the attacker does not have the complete inputs necessary. + * The privateSalt property exists to satisfy this private-and-not-shared part of the salt.

+ *

If you configure this attribute, you can obtain this additional very important safety feature.

+ * @param staticSalt the static salt + */ + public final void setStaticSalt(final String staticSalt) { + this.staticSalt = staticSalt; + } + + /** + * Sets password field name. Default is {@link #DEFAULT_PASSWORD_FIELD}. + * + * @param passwordFieldName the password field name + */ + public final void setPasswordFieldName(final String passwordFieldName) { + this.passwordFieldName = passwordFieldName; + } + + /** + * Sets salt field name. Default is {@link #DEFAULT_SALT_FIELD}. + * + * @param saltFieldName the password field name + */ + public final void setSaltFieldName(final String saltFieldName) { + this.saltFieldName = saltFieldName; + } + + /** + * Sets number of iterations field name. Default is {@link #DEFAULT_NUM_ITERATIONS_FIELD}. + * + * @param numberOfIterationsFieldName the password field name + */ + public final void setNumberOfIterationsFieldName(final String numberOfIterationsFieldName) { + this.numberOfIterationsFieldName = numberOfIterationsFieldName; + } + + /** + * Sets number of iterations. Default is 0. + * + * @param numberOfIterations the number of iterations + */ + public final void setNumberOfIterations(final long numberOfIterations) { + this.numberOfIterations = numberOfIterations; + } + +} diff --git a/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/QueryDatabaseAuthenticationHandler.java b/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/QueryDatabaseAuthenticationHandler.java new file mode 100644 index 000000000000..f6ef2f36ff29 --- /dev/null +++ b/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/QueryDatabaseAuthenticationHandler.java @@ -0,0 +1,81 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.jdbc; + +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.IncorrectResultSizeDataAccessException; + +import javax.security.auth.login.AccountNotFoundException; +import javax.security.auth.login.FailedLoginException; +import javax.validation.constraints.NotNull; +import java.security.GeneralSecurityException; + +/** + * Class that if provided a query that returns a password (parameter of query + * must be username) will compare that password to a translated version of the + * password provided by the user. If they match, then authentication succeeds. + * Default password translator is plaintext translator. + * + * @author Scott Battaglia + * @author Dmitriy Kopylenko + * @author Marvin S. Addison + * + * @since 3.0.0 + */ +public class QueryDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler { + + @NotNull + private String sql; + + /** + * {@inheritDoc} + */ + @Override + protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential) + throws GeneralSecurityException, PreventedException { + + final String username = credential.getUsername(); + final String encryptedPassword = this.getPasswordEncoder().encode(credential.getPassword()); + try { + final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username); + if (!dbPassword.equals(encryptedPassword)) { + throw new FailedLoginException("Password does not match value on record."); + } + } catch (final IncorrectResultSizeDataAccessException e) { + if (e.getActualSize() == 0) { + throw new AccountNotFoundException(username + " not found with SQL query"); + } else { + throw new FailedLoginException("Multiple records found for " + username); + } + } catch (final DataAccessException e) { + throw new PreventedException("SQL exception while executing query for " + username, e); + } + return createHandlerResult(credential, this.principalFactory.createPrincipal(username), null); + } + + /** + * @param sql The sql to set. + */ + public void setSql(final String sql) { + this.sql = sql; + } +} diff --git a/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/SearchModeSearchDatabaseAuthenticationHandler.java b/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/SearchModeSearchDatabaseAuthenticationHandler.java new file mode 100644 index 000000000000..02f5f92e7225 --- /dev/null +++ b/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/SearchModeSearchDatabaseAuthenticationHandler.java @@ -0,0 +1,108 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.jdbc; + +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.dao.DataAccessException; + +import javax.security.auth.login.FailedLoginException; +import javax.validation.constraints.NotNull; +import java.security.GeneralSecurityException; + +/** + * Class that given a table, username field and password field will query a + * database table with the provided encryption technique to see if the user + * exists. This class defaults to a PasswordTranslator of + * PlainTextPasswordTranslator. + * + * @author Scott Battaglia + * @author Dmitriy Kopylenko + * @author Marvin S. Addison + * + * @since 3.0.0 + */ + +public class SearchModeSearchDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler + implements InitializingBean { + + private static final String SQL_PREFIX = "Select count('x') from "; + + @NotNull + private String fieldUser; + + @NotNull + private String fieldPassword; + + @NotNull + private String tableUsers; + + private String sql; + + /** + * {@inheritDoc} + */ + @Override + protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential) + throws GeneralSecurityException, PreventedException { + + final String username = credential.getUsername(); + final String encyptedPassword = getPasswordEncoder().encode(credential.getPassword()); + final int count; + try { + count = getJdbcTemplate().queryForObject(this.sql, Integer.class, username, encyptedPassword); + } catch (final DataAccessException e) { + throw new PreventedException("SQL exception while executing query for " + username, e); + } + if (count == 0) { + throw new FailedLoginException(username + " not found with SQL query."); + } + return createHandlerResult(credential, this.principalFactory.createPrincipal(username), null); + } + + @Override + public void afterPropertiesSet() throws Exception { + this.sql = SQL_PREFIX + this.tableUsers + " WHERE " + this.fieldUser + " = ? AND " + this.fieldPassword + + " = ?"; + } + + /** + * @param fieldPassword The fieldPassword to set. + */ + public final void setFieldPassword(final String fieldPassword) { + this.fieldPassword = fieldPassword; + } + + /** + * @param fieldUser The fieldUser to set. + */ + public final void setFieldUser(final String fieldUser) { + this.fieldUser = fieldUser; + } + + /** + * @param tableUsers The tableUsers to set. + */ + public final void setTableUsers(final String tableUsers) { + this.tableUsers = tableUsers; + } + +} diff --git a/cas-server-support-jdbc/src/main/java/org/jasig/cas/monitor/DataSourceMonitor.java b/cas-server-support-jdbc/src/main/java/org/jasig/cas/monitor/DataSourceMonitor.java new file mode 100644 index 000000000000..d67bf8c54f89 --- /dev/null +++ b/cas-server-support-jdbc/src/main/java/org/jasig/cas/monitor/DataSourceMonitor.java @@ -0,0 +1,88 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; + +import javax.sql.DataSource; +import javax.validation.constraints.NotNull; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Monitors a data source that describes a single connection or connection pool to a database. + * + * @author Marvin S. Addison + * @since 3.5.1 + */ +public class DataSourceMonitor extends AbstractPoolMonitor { + + @NotNull + private final JdbcTemplate jdbcTemplate; + + @NotNull + private String validationQuery; + + + /** + * Creates a new instance that monitors the given data source. + * + * @param dataSource Data source to monitor. + */ + public DataSourceMonitor(final DataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + + /** + * Sets the validation query used to monitor the data source. The validation query should return + * at least one result; otherwise results are ignored. + * + * @param query Validation query that should be as efficient as possible. + */ + public void setValidationQuery(final String query) { + this.validationQuery = query; + } + + + @Override + protected StatusCode checkPool() throws Exception { + return this.jdbcTemplate.query(this.validationQuery, new ResultSetExtractor() { + public StatusCode extractData(final ResultSet rs) throws SQLException { + if (rs.next()) { + return StatusCode.OK; + } + return StatusCode.WARN; + } + }); + } + + + @Override + protected int getIdleCount() { + return PoolStatus.UNKNOWN_COUNT; + } + + + @Override + protected int getActiveCount() { + return PoolStatus.UNKNOWN_COUNT; + } +} diff --git a/cas-server-support-jdbc/src/main/java/org/jasig/cas/services/JpaServiceRegistryDaoImpl.java b/cas-server-support-jdbc/src/main/java/org/jasig/cas/services/JpaServiceRegistryDaoImpl.java new file mode 100644 index 000000000000..1879cf39449e --- /dev/null +++ b/cas-server-support-jdbc/src/main/java/org/jasig/cas/services/JpaServiceRegistryDaoImpl.java @@ -0,0 +1,77 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * Implementation of the ServiceRegistryDao based on JPA. + * + * @author Scott Battaglia + * @since 3.1 + */ +public final class JpaServiceRegistryDaoImpl implements ServiceRegistryDao { + + @NotNull + @PersistenceContext + private EntityManager entityManager; + + @Transactional(readOnly = false) + @Override + public boolean delete(final RegisteredService registeredService) { + if (this.entityManager.contains(registeredService)) { + this.entityManager.remove(registeredService); + } else { + this.entityManager.remove(this.entityManager.merge(registeredService)); + } + return true; + } + + @Transactional(readOnly = true) + @Override + public List load() { + return this.entityManager.createQuery("select r from AbstractRegisteredService r", RegisteredService.class) + .getResultList(); + } + + @Transactional(readOnly = false) + @Override + public RegisteredService save(final RegisteredService registeredService) { + final boolean isNew = registeredService.getId() == RegisteredService.INITIAL_IDENTIFIER_VALUE; + + final RegisteredService r = this.entityManager.merge(registeredService); + + if (!isNew) { + this.entityManager.persist(r); + } + + return r; + } + + @Transactional(readOnly = true) + @Override + public RegisteredService findServiceById(final long id) { + return this.entityManager.find(AbstractRegisteredService.class, id); + } +} diff --git a/cas-server-support-jdbc/src/main/java/org/jasig/cas/ticket/registry/JpaTicketRegistry.java b/cas-server-support-jdbc/src/main/java/org/jasig/cas/ticket/registry/JpaTicketRegistry.java new file mode 100644 index 000000000000..efc35f5ef6c3 --- /dev/null +++ b/cas-server-support-jdbc/src/main/java/org/jasig/cas/ticket/registry/JpaTicketRegistry.java @@ -0,0 +1,218 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.LockModeType; +import javax.persistence.PersistenceContext; +import javax.validation.constraints.NotNull; + +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.ServiceTicketImpl; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.springframework.transaction.annotation.Transactional; + +/** + * JPA implementation of a CAS {@link TicketRegistry}. This implementation of + * ticket registry is suitable for HA environments. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * + * @since 3.2.1 + * + */ +public final class JpaTicketRegistry extends AbstractDistributedTicketRegistry { + + @NotNull + @PersistenceContext + private EntityManager entityManager; + + @NotNull + private String ticketGrantingTicketPrefix = "TGT"; + + @Transactional(readOnly = false) + @Override + protected void updateTicket(final Ticket ticket) { + entityManager.merge(ticket); + logger.debug("Updated ticket [{}].", ticket); + } + + @Transactional(readOnly = false) + @Override + public void addTicket(final Ticket ticket) { + entityManager.persist(ticket); + logger.debug("Added ticket [{}] to registry.", ticket); + } + + @Transactional(readOnly = false) + @Override + public boolean deleteTicket(final String ticketId) { + final Ticket ticket = getRawTicket(ticketId); + + if (ticket == null) { + return false; + } + + if (ticket instanceof ServiceTicket) { + removeTicket(ticket); + logger.debug("Deleted ticket [{}] from the registry.", ticket); + return true; + } + + deleteTicketAndChildren(ticket); + logger.debug("Deleted ticket [{}] and its children from the registry.", ticket); + return true; + } + + /** + * Delete the TGt and all of its service tickets. + * + * @param ticket the ticket + */ + private void deleteTicketAndChildren(final Ticket ticket) { + final List ticketGrantingTicketImpls = entityManager + .createQuery("select t from TicketGrantingTicketImpl t where t.ticketGrantingTicket.id = :id", + TicketGrantingTicketImpl.class) + .setLockMode(LockModeType.PESSIMISTIC_WRITE) + .setParameter("id", ticket.getId()) + .getResultList(); + final List serviceTicketImpls = entityManager + .createQuery("select s from ServiceTicketImpl s where s.ticketGrantingTicket.id = :id", + ServiceTicketImpl.class) + .setParameter("id", ticket.getId()) + .getResultList(); + + for (final ServiceTicketImpl s : serviceTicketImpls) { + removeTicket(s); + } + + for (final TicketGrantingTicketImpl t : ticketGrantingTicketImpls) { + deleteTicketAndChildren(t); + } + + removeTicket(ticket); + } + + /** + * Removes the ticket. + * + * @param ticket the ticket + */ + private void removeTicket(final Ticket ticket) { + try { + if (logger.isDebugEnabled()) { + final Date creationDate = new Date(ticket.getCreationTime()); + logger.debug("Removing Ticket [{}] created: {}", ticket, creationDate.toString()); + } + entityManager.remove(ticket); + } catch (final Exception e) { + logger.error("Error removing {} from registry.", ticket, e); + } + } + + @Transactional(readOnly=true) + @Override + public Ticket getTicket(final String ticketId) { + return getProxiedTicketInstance(getRawTicket(ticketId)); + } + + /** + * Gets the ticket from the database, as is. + * + * @param ticketId the ticket id + * @return the raw ticket + */ + private Ticket getRawTicket(final String ticketId) { + try { + if (ticketId.startsWith(this.ticketGrantingTicketPrefix)) { + return entityManager.find(TicketGrantingTicketImpl.class, ticketId, LockModeType.PESSIMISTIC_WRITE); + } + + return entityManager.find(ServiceTicketImpl.class, ticketId); + } catch (final Exception e) { + logger.error("Error getting ticket {} from registry.", ticketId, e); + } + return null; + } + + @Transactional(readOnly=true) + @Override + public Collection getTickets() { + final List tgts = entityManager + .createQuery("select t from TicketGrantingTicketImpl t", TicketGrantingTicketImpl.class) + .getResultList(); + final List sts = entityManager + .createQuery("select s from ServiceTicketImpl s", ServiceTicketImpl.class) + .getResultList(); + + final List tickets = new ArrayList<>(); + tickets.addAll(tgts); + tickets.addAll(sts); + + return tickets; + } + + public void setTicketGrantingTicketPrefix(final String ticketGrantingTicketPrefix) { + this.ticketGrantingTicketPrefix = ticketGrantingTicketPrefix; + } + + @Override + protected boolean needsCallback() { + return false; + } + + @Transactional(readOnly=true) + @Override + public int sessionCount() { + return countToInt(entityManager.createQuery( + "select count(t) from TicketGrantingTicketImpl t").getSingleResult()); + } + + @Transactional(readOnly=true) + @Override + public int serviceTicketCount() { + return countToInt(entityManager.createQuery("select count(t) from ServiceTicketImpl t").getSingleResult()); + } + + /** + * Count the result into a numeric value. + * + * @param result the result + * @return the int + */ + private int countToInt(final Object result) { + final int intval; + if (result instanceof Long) { + intval = ((Long) result).intValue(); + } else if (result instanceof Integer) { + intval = (Integer) result; + } else { + // Must be a Number of some kind + intval = ((Number) result).intValue(); + } + return intval; + } +} diff --git a/cas-server-support-jdbc/src/main/java/org/jasig/cas/ticket/registry/support/JpaLockingStrategy.java b/cas-server-support-jdbc/src/main/java/org/jasig/cas/ticket/registry/support/JpaLockingStrategy.java new file mode 100644 index 000000000000..8fd49cf35cea --- /dev/null +++ b/cas-server-support-jdbc/src/main/java/org/jasig/cas/ticket/registry/support/JpaLockingStrategy.java @@ -0,0 +1,292 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry.support; + +import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.Id; +import javax.persistence.LockModeType; +import javax.persistence.PersistenceContext; +import javax.persistence.PersistenceException; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.validation.constraints.NotNull; +import java.util.Calendar; +import java.util.Date; + +/** + * JPA 2.0 implementation of an exclusive, non-reentrant lock. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +public class JpaLockingStrategy implements LockingStrategy { + + /** Default lock timeout is 1 hour. */ + public static final int DEFAULT_LOCK_TIMEOUT = 3600; + + /** Transactional entity manager from Spring context. */ + @NotNull + @PersistenceContext + protected EntityManager entityManager; + + /** Logger instance. */ + private final Logger logger = LoggerFactory.getLogger(getClass()); + + /** + * Application identifier that identifies rows in the locking table, + * each one of which may be for a different application or usage within + * a single application. + */ + @NotNull + private String applicationId; + + /** Unique identifier that identifies the client using this lock instance. */ + @NotNull + private String uniqueId; + + /** Amount of time in seconds lock may be held. */ + private int lockTimeout = DEFAULT_LOCK_TIMEOUT; + + + /** + * @param id Application identifier that identifies a row in the lock + * table for which multiple clients vie to hold the lock. + * This must be the same for all clients contending for a + * particular lock. + */ + public void setApplicationId(final String id) { + this.applicationId = id; + } + + + /** + * @param id Identifier used to identify this instance in a row of the + * lock table. Must be unique across all clients vying for + * locks for a given application ID. + */ + public void setUniqueId(final String id) { + this.uniqueId = id; + } + + + /** + * @param seconds Maximum amount of time in seconds lock may be held. + * A value of zero indicates that locks are held indefinitely. + * Use of a reasonable timeout facilitates recovery from node failures, + * so setting to zero is discouraged. + */ + public void setLockTimeout(final int seconds) { + if (seconds < 0) { + throw new IllegalArgumentException("Lock timeout must be non-negative."); + } + this.lockTimeout = seconds; + } + + /** + * {@inheritDoc} + **/ + @Override + @Transactional(readOnly = false) + public boolean acquire() { + Lock lock; + try { + lock = entityManager.find(Lock.class, applicationId, LockModeType.PESSIMISTIC_WRITE); + } catch (final PersistenceException e) { + logger.debug("{} failed querying for {} lock.", uniqueId, applicationId, e); + return false; + } + + boolean result = false; + if (lock != null) { + final DateTime expDate = new DateTime(lock.getExpirationDate()); + if (lock.getUniqueId() == null) { + // No one currently possesses lock + logger.debug("{} trying to acquire {} lock.", uniqueId, applicationId); + result = acquire(entityManager, lock); + } else if (new DateTime().isAfter(expDate)) { + // Acquire expired lock regardless of who formerly owned it + logger.debug("{} trying to acquire expired {} lock.", uniqueId, applicationId); + result = acquire(entityManager, lock); + } + } else { + // First acquisition attempt for this applicationId + logger.debug("Creating {} lock initially held by {}.", applicationId, uniqueId); + result = acquire(entityManager, new Lock()); + } + return result; + } + + /** + * {@inheritDoc} + **/ + @Override + @Transactional(readOnly = false) + public void release() { + final Lock lock = entityManager.find(Lock.class, applicationId, LockModeType.PESSIMISTIC_WRITE); + + if (lock == null) { + return; + } + // Only the current owner can release the lock + final String owner = lock.getUniqueId(); + if (uniqueId.equals(owner)) { + lock.setUniqueId(null); + lock.setExpirationDate(null); + logger.debug("Releasing {} lock held by {}.", applicationId, uniqueId); + entityManager.persist(lock); + } else { + throw new IllegalStateException("Cannot release lock owned by " + owner); + } + } + + /** + * Gets the current owner of the lock as determined by querying for + * uniqueId. + * + * @return Current lock owner or null if no one presently owns lock. + */ + @Transactional(readOnly = true) + public String getOwner() { + final Lock lock = entityManager.find(Lock.class, applicationId); + if (lock != null) { + return lock.getUniqueId(); + } + return null; + } + + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return uniqueId; + } + + /** + * Acquire the lock object. + * + * @param em the em + * @param lock the lock + * @return true, if successful + */ + private boolean acquire(final EntityManager em, final Lock lock) { + lock.setUniqueId(uniqueId); + if (lockTimeout > 0) { + final Calendar cal = Calendar.getInstance(); + cal.add(Calendar.SECOND, lockTimeout); + lock.setExpirationDate(cal.getTime()); + } else { + lock.setExpirationDate(null); + } + boolean success = false; + try { + if (lock.getApplicationId() != null) { + em.merge(lock); + } else { + lock.setApplicationId(applicationId); + em.persist(lock); + } + success = true; + } catch (final PersistenceException e) { + success = false; + if (logger.isDebugEnabled()) { + logger.debug("{} could not obtain {} lock.", uniqueId, applicationId, e); + } else { + logger.info("{} could not obtain {} lock.", uniqueId, applicationId); + } + } + return success; + } + + + /** + * Describes a database lock. + * + * @author Marvin S. Addison + * + */ + @Entity + @Table(name = "locks") + private static class Lock { + /** column name that holds application identifier. */ + @Id + @Column(name="application_id") + private String applicationId; + + /** Database column name that holds unique identifier. */ + @Column(name="unique_id") + private String uniqueId; + + /** Database column name that holds expiration date. */ + @Temporal(TemporalType.TIMESTAMP) + @Column(name="expiration_date") + private Date expirationDate; + + /** + * @return the applicationId + */ + public String getApplicationId() { + return applicationId; + } + + /** + * @param applicationId the applicationId to set + */ + public void setApplicationId(final String applicationId) { + this.applicationId = applicationId; + } + + /** + * @return the uniqueId + */ + public String getUniqueId() { + return uniqueId; + } + + /** + * @param uniqueId the uniqueId to set + */ + public void setUniqueId(final String uniqueId) { + this.uniqueId = uniqueId; + } + + /** + * @return the expirationDate + */ + public DateTime getExpirationDate() { + return this.expirationDate == null ? null : new DateTime(this.expirationDate); + } + + /** + * @param expirationDate the expirationDate to set + */ + public void setExpirationDate(final Date expirationDate) { + this.expirationDate = expirationDate; + } + } +} diff --git a/cas-server-support-jdbc/src/site/site.xml b/cas-server-support-jdbc/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-support-jdbc/src/site/site.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/cas-server-3.4.2/cas-server-support-generic/src/test/clover/clover.license b/cas-server-support-jdbc/src/test/clover/clover.license similarity index 100% rename from cas-server-3.4.2/cas-server-support-generic/src/test/clover/clover.license rename to cas-server-support-jdbc/src/test/clover/clover.license diff --git a/cas-server-support-jdbc/src/test/java/org/jasig/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandlerTests.java b/cas-server-support-jdbc/src/test/java/org/jasig/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandlerTests.java new file mode 100644 index 000000000000..13c9f784032f --- /dev/null +++ b/cas-server-support-jdbc/src/test/java/org/jasig/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandlerTests.java @@ -0,0 +1,205 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.jdbc; + +import org.apache.commons.codec.digest.MessageDigestAlgorithms; +import org.apache.shiro.crypto.hash.DefaultHashService; +import org.apache.shiro.crypto.hash.HashRequest; +import org.apache.shiro.util.ByteSource; +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.handler.PasswordEncoder; +import org.jasig.cas.authentication.handler.PrefixSuffixPrincipalNameTransformer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.auth.login.FailedLoginException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.Statement; + +import static org.junit.Assert.*; + +/** + * @author Misagh Moayyed + * @since 4.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:/jpaTestApplicationContext.xml") +public class QueryAndEncodeDatabaseAuthenticationHandlerTests { + private static final String ALG_NAME = MessageDigestAlgorithms.SHA_512; + private static final String SQL = "SELECT * FROM users where %s"; + private static final int NUM_ITERATIONS = 5; + private static final String STATIC_SALT = "STATIC_SALT"; + + @Autowired + private DataSource dataSource; + + @Before + public void setup() throws Exception { + final Connection c = this.dataSource.getConnection(); + final Statement s = c.createStatement(); + c.setAutoCommit(true); + + s.execute(getSqlInsertStatementToCreateUserAccount(0)); + for (int i = 0; i < 10; i++) { + s.execute(getSqlInsertStatementToCreateUserAccount(i)); + } + + c.close(); + } + + private String getSqlInsertStatementToCreateUserAccount(final int i) { + final String psw = genPassword("user" + i, "salt" + i, NUM_ITERATIONS); + + final String sql = String.format( + "insert into users (username, password, salt, numIterations) values('%s', '%s', '%s', %s);", + "user" + i, psw, "salt" + i, NUM_ITERATIONS); + return sql; + } + + @After + public void tearDown() throws Exception { + final Connection c = this.dataSource.getConnection(); + final Statement s = c.createStatement(); + c.setAutoCommit(true); + + for (int i = 0; i < 5; i++) { + final String sql = String.format("delete from users;"); + s.execute(sql); + } + c.close(); + } + + @Test(expected = AccountNotFoundException.class) + public void verifyAuthenticationFailsToFindUser() throws Exception { + final QueryAndEncodeDatabaseAuthenticationHandler q = + new QueryAndEncodeDatabaseAuthenticationHandler(this.dataSource, buildSql(), + ALG_NAME); + q.authenticateUsernamePasswordInternal(TestUtils.getCredentialsWithSameUsernameAndPassword()); + + } + + @Test(expected = PreventedException.class) + public void verifyAuthenticationInvalidSql() throws Exception { + final QueryAndEncodeDatabaseAuthenticationHandler q = + new QueryAndEncodeDatabaseAuthenticationHandler(this.dataSource, buildSql("makesNoSenseInSql"), + ALG_NAME); + q.authenticateUsernamePasswordInternal(TestUtils.getCredentialsWithSameUsernameAndPassword()); + + } + + @Test(expected = FailedLoginException.class) + public void verifyAuthenticationMultipleAccounts() throws Exception { + final QueryAndEncodeDatabaseAuthenticationHandler q = + new QueryAndEncodeDatabaseAuthenticationHandler(this.dataSource, buildSql(), + ALG_NAME); + q.authenticateUsernamePasswordInternal( + TestUtils.getCredentialsWithDifferentUsernameAndPassword("user0", "password0")); + + } + + @Test + public void verifyAuthenticationSuccessful() throws Exception { + final QueryAndEncodeDatabaseAuthenticationHandler q = + new QueryAndEncodeDatabaseAuthenticationHandler(this.dataSource, buildSql(), + ALG_NAME); + q.setNumberOfIterationsFieldName("numIterations"); + q.setStaticSalt(STATIC_SALT); + + final UsernamePasswordCredential c = TestUtils.getCredentialsWithSameUsernameAndPassword("user1"); + final HandlerResult r = q.authenticateUsernamePasswordInternal(c); + + assertNotNull(r); + assertEquals(r.getPrincipal().getId(), "user1"); + } + + @Test + public void verifyAuthenticationSuccessfulWithAPasswordEncoder() throws Exception { + final QueryAndEncodeDatabaseAuthenticationHandler q = + new QueryAndEncodeDatabaseAuthenticationHandler(this.dataSource, buildSql(), + ALG_NAME); + q.setNumberOfIterationsFieldName("numIterations"); + q.setStaticSalt(STATIC_SALT); + q.setPasswordEncoder(new PasswordEncoder() { + @Override + public String encode(final String password) { + return password.concat("1"); + } + }); + + q.setPrincipalNameTransformer(new PrefixSuffixPrincipalNameTransformer("user", null)); + final HandlerResult r = q.authenticateUsernamePasswordInternal( + TestUtils.getCredentialsWithDifferentUsernameAndPassword("1", "user")); + + assertNotNull(r); + assertEquals(r.getPrincipal().getId(), "user1"); + } + + private String buildSql(final String where) { + return String.format(SQL, where); + } + + private String buildSql() { + return String.format(SQL, "username=?;"); + } + + + private String genPassword(final String psw, final String salt, final int iter) { + try { + + final DefaultHashService hash = new DefaultHashService(); + hash.setPrivateSalt(ByteSource.Util.bytes(STATIC_SALT)); + hash.setHashIterations(iter); + hash.setGeneratePublicSalt(false); + hash.setHashAlgorithmName(ALG_NAME); + + final String pswEnc = hash.computeHash(new HashRequest.Builder() + .setSource(psw).setSalt(salt).setIterations(iter).build()).toHex(); + + return pswEnc; + + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + @Entity(name="users") + public static class UsersTable { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String username; + private String password; + private String salt; + private long numIterations; + } +} diff --git a/cas-server-support-jdbc/src/test/java/org/jasig/cas/adaptors/jdbc/QueryDatabaseAuthenticationHandlerTests.java b/cas-server-support-jdbc/src/test/java/org/jasig/cas/adaptors/jdbc/QueryDatabaseAuthenticationHandlerTests.java new file mode 100644 index 000000000000..41fddbf30673 --- /dev/null +++ b/cas-server-support-jdbc/src/test/java/org/jasig/cas/adaptors/jdbc/QueryDatabaseAuthenticationHandlerTests.java @@ -0,0 +1,148 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.adaptors.jdbc; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.PreventedException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.auth.login.FailedLoginException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.Statement; + +import static org.junit.Assert.*; + +/** + * This is tests for {@link org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler}. + * + * @author Misagh Moayyed mmoayyed@unicon.net + * @since 4.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:/jpaTestApplicationContext.xml") +public class QueryDatabaseAuthenticationHandlerTests { + + private static final String SQL = "SELECT password FROM casusers where username=?"; + + @Autowired + private DataSource dataSource; + + @Before + public void setup() throws Exception { + final Connection c = this.dataSource.getConnection(); + final Statement s = c.createStatement(); + c.setAutoCommit(true); + + s.execute(getSqlInsertStatementToCreateUserAccount(0)); + for (int i = 0; i < 10; i++) { + s.execute(getSqlInsertStatementToCreateUserAccount(i)); + } + + c.close(); + } + + @After + public void tearDown() throws Exception { + final Connection c = this.dataSource.getConnection(); + final Statement s = c.createStatement(); + c.setAutoCommit(true); + + for (int i = 0; i < 5; i++) { + final String sql = String.format("delete from casusers;"); + s.execute(sql); + } + c.close(); + } + + private String getSqlInsertStatementToCreateUserAccount(final int i) { + return String.format("insert into casusers (username, password) values('%s', '%s');", "user" + i, "psw" + i); + } + + @Entity(name="casusers") + public static class UsersTable { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String username; + private String password; + } + + @Test(expected = AccountNotFoundException.class) + public void verifyAuthenticationFailsToFindUser() throws Exception { + final QueryDatabaseAuthenticationHandler q = new QueryDatabaseAuthenticationHandler(); + q.setDataSource(this.dataSource); + q.setSql(SQL); + q.authenticateUsernamePasswordInternal( + TestUtils.getCredentialsWithDifferentUsernameAndPassword("usernotfound", "psw1")); + + } + + @Test(expected = FailedLoginException.class) + public void verifyPasswordInvalid() throws Exception { + final QueryDatabaseAuthenticationHandler q = new QueryDatabaseAuthenticationHandler(); + q.setDataSource(this.dataSource); + q.setSql(SQL); + q.authenticateUsernamePasswordInternal( + TestUtils.getCredentialsWithDifferentUsernameAndPassword("user1", "psw11")); + + } + + @Test(expected = FailedLoginException.class) + public void verifyMultipleRecords() throws Exception { + final QueryDatabaseAuthenticationHandler q = new QueryDatabaseAuthenticationHandler(); + q.setDataSource(this.dataSource); + q.setSql(SQL); + q.authenticateUsernamePasswordInternal( + TestUtils.getCredentialsWithDifferentUsernameAndPassword("user0", "psw0")); + + } + + @Test(expected = PreventedException.class) + public void verifyBadQuery() throws Exception { + final QueryDatabaseAuthenticationHandler q = new QueryDatabaseAuthenticationHandler(); + q.setDataSource(this.dataSource); + q.setSql(SQL.replace("password", "*")); + q.authenticateUsernamePasswordInternal( + TestUtils.getCredentialsWithDifferentUsernameAndPassword("user0", "psw0")); + + } + + public void verifySuccess() throws Exception { + final QueryDatabaseAuthenticationHandler q = new QueryDatabaseAuthenticationHandler(); + q.setDataSource(this.dataSource); + q.setSql(SQL); + assertNotNull(q.authenticateUsernamePasswordInternal( + TestUtils.getCredentialsWithDifferentUsernameAndPassword("user3", "psw3"))); + + } +} diff --git a/cas-server-support-jdbc/src/test/java/org/jasig/cas/adaptors/jdbc/SearchModeSearchDatabaseAuthenticationHandlerTests.java b/cas-server-support-jdbc/src/test/java/org/jasig/cas/adaptors/jdbc/SearchModeSearchDatabaseAuthenticationHandlerTests.java new file mode 100644 index 000000000000..1584a6c4fb4f --- /dev/null +++ b/cas-server-support-jdbc/src/test/java/org/jasig/cas/adaptors/jdbc/SearchModeSearchDatabaseAuthenticationHandlerTests.java @@ -0,0 +1,125 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.adaptors.jdbc; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.security.auth.login.FailedLoginException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.Statement; + +import static org.junit.Assert.*; + +/** + * Tests for {@link org.jasig.cas.adaptors.jdbc.SearchModeSearchDatabaseAuthenticationHandler}. + * + * @author Misagh Moayyed mmoayyed@unicon.net + * @since 4.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:/jpaTestApplicationContext.xml") +public class SearchModeSearchDatabaseAuthenticationHandlerTests { + + private SearchModeSearchDatabaseAuthenticationHandler handler; + + @Autowired + private DataSource dataSource; + + @Before + public void setup() throws Exception { + this.handler = new SearchModeSearchDatabaseAuthenticationHandler(); + handler.setDataSource(this.dataSource); + handler.setTableUsers("cassearchusers"); + handler.setFieldUser("username"); + handler.setFieldPassword("password"); + handler.afterPropertiesSet(); + + final Connection c = this.dataSource.getConnection(); + final Statement s = c.createStatement(); + c.setAutoCommit(true); + + s.execute(getSqlInsertStatementToCreateUserAccount(0)); + for (int i = 0; i < 10; i++) { + s.execute(getSqlInsertStatementToCreateUserAccount(i)); + } + + c.close(); + } + + @After + public void tearDown() throws Exception { + final Connection c = this.dataSource.getConnection(); + final Statement s = c.createStatement(); + c.setAutoCommit(true); + + for (int i = 0; i < 5; i++) { + final String sql = String.format("delete from casusers;"); + s.execute(sql); + } + c.close(); + } + + private String getSqlInsertStatementToCreateUserAccount(final int i) { + return String.format("insert into cassearchusers (username, password) values('%s', '%s');", "user" + i, "psw" + i); + } + + @Entity(name="cassearchusers") + public static class UsersTable { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String username; + private String password; + } + + @Test(expected = FailedLoginException.class) + public void verifyNotFoundUser() throws Exception { + final UsernamePasswordCredential c = TestUtils.getCredentialsWithDifferentUsernameAndPassword("hello", "world"); + this.handler.authenticateUsernamePasswordInternal(c); + } + + @Test + public void verifyFoundUser() throws Exception { + final UsernamePasswordCredential c = TestUtils.getCredentialsWithDifferentUsernameAndPassword("user3", "psw3"); + assertNotNull(this.handler.authenticateUsernamePasswordInternal(c)); + } + + @Test + public void verifyMultipleUsersFound() throws Exception { + final UsernamePasswordCredential c = TestUtils.getCredentialsWithDifferentUsernameAndPassword("user0", "psw0"); + assertNotNull(this.handler.authenticateUsernamePasswordInternal(c)); + } + +} + diff --git a/cas-server-support-jdbc/src/test/java/org/jasig/cas/monitor/DataSourceMonitorTests.java b/cas-server-support-jdbc/src/test/java/org/jasig/cas/monitor/DataSourceMonitorTests.java new file mode 100644 index 000000000000..5128c0ad14ed --- /dev/null +++ b/cas-server-support-jdbc/src/test/java/org/jasig/cas/monitor/DataSourceMonitorTests.java @@ -0,0 +1,53 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import javax.sql.DataSource; +import java.util.concurrent.Executors; + +import static org.junit.Assert.assertEquals; + +/** + * Unit test for {@link DataSourceMonitor}. + * + * @author Marvin S. Addison + * @since 3.5.1 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:/jpaTestApplicationContext.xml") +public class DataSourceMonitorTests { + + @Autowired + private DataSource dataSource; + + @Test + public void verifyObserve() throws Exception { + final DataSourceMonitor monitor = new DataSourceMonitor(this.dataSource); + monitor.setExecutor(Executors.newSingleThreadExecutor()); + monitor.setValidationQuery("SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS"); + final PoolStatus status = monitor.observe(); + assertEquals(StatusCode.OK, status.getCode()); + } +} diff --git a/cas-server-support-jdbc/src/test/java/org/jasig/cas/monitor/SessionMonitorJpaTests.java b/cas-server-support-jdbc/src/test/java/org/jasig/cas/monitor/SessionMonitorJpaTests.java new file mode 100644 index 000000000000..65c28ab0046b --- /dev/null +++ b/cas-server-support-jdbc/src/test/java/org/jasig/cas/monitor/SessionMonitorJpaTests.java @@ -0,0 +1,96 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.mock.MockService; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.jasig.cas.ticket.registry.JpaTicketRegistry; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.jasig.cas.ticket.support.HardTimeoutExpirationPolicy; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.Rollback; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +import static org.junit.Assert.assertEquals; + +/** + * Unit test for {@link org.jasig.cas.monitor.SessionMonitor} class that involves {@link JpaTicketRegistry}. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations={"classpath:/jpaTestApplicationContext.xml"}) +@Transactional +public class SessionMonitorJpaTests { + + private static final ExpirationPolicy TEST_EXP_POLICY = new HardTimeoutExpirationPolicy(10000); + private static final UniqueTicketIdGenerator GENERATOR = new DefaultUniqueTicketIdGenerator(); + + @Autowired + private JpaTicketRegistry jpaRegistry; + private SessionMonitor monitor; + + @Before + public void setUp() { + this.monitor = new SessionMonitor(); + } + + @Test + @Rollback(false) + public void verifyObserveOkJpaTicketRegistry() throws Exception { + addTicketsToRegistry(this.jpaRegistry, 5, 5); + assertEquals(10, this.jpaRegistry.getTickets().size()); + this.monitor.setTicketRegistry(this.jpaRegistry); + final SessionStatus status = this.monitor.observe(); + assertEquals(5, status.getSessionCount()); + assertEquals(5, status.getServiceTicketCount()); + assertEquals(StatusCode.OK, status.getCode()); + } + + private void addTicketsToRegistry(final TicketRegistry registry, final int tgtCount, final int stCount) { + TicketGrantingTicketImpl ticket = null; + for (int i = 0; i < tgtCount; i++) { + ticket = new TicketGrantingTicketImpl( + GENERATOR.getNewTicketId("TGT"), + TestUtils.getAuthentication(), + TEST_EXP_POLICY); + registry.addTicket(ticket); + } + + if (ticket != null) { + for (int i = 0; i < stCount; i++) { + registry.addTicket(ticket.grantServiceTicket( + GENERATOR.getNewTicketId("ST"), + new MockService("junit"), + TEST_EXP_POLICY, + false)); + } + } + } +} diff --git a/cas-server-support-jdbc/src/test/java/org/jasig/cas/services/JpaServiceRegistryDaoImplTests.java b/cas-server-support-jdbc/src/test/java/org/jasig/cas/services/JpaServiceRegistryDaoImplTests.java new file mode 100644 index 000000000000..5072597f478c --- /dev/null +++ b/cas-server-support-jdbc/src/test/java/org/jasig/cas/services/JpaServiceRegistryDaoImplTests.java @@ -0,0 +1,100 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; + +import java.util.List; + +import static org.junit.Assert.*; + +/** + * Handles tests for {@link JpaServiceRegistryDaoImpl} + * @author battags + * @since 3.1.0 + * + */ +@ContextConfiguration(locations= {"classpath:jpaTestApplicationContext.xml"}) +public class JpaServiceRegistryDaoImplTests extends AbstractTransactionalJUnit4SpringContextTests { + + @Autowired(required=true) + private JpaServiceRegistryDaoImpl dao; + + @Test + public void verifySaveMethodWithNonExistentServiceAndNoAttributes() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setName("test"); + r.setServiceId("testId"); + r.setTheme("theme"); + r.setDescription("description"); + + final RegisteredService r2 = this.dao.save(r); + final RegisteredService r3 = this.dao.findServiceById(r2.getId()); + + assertEquals(r, r2); + assertEquals(r2, r3); + } + + @Test + public void verifySaveAttributeReleasePolicy() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setName("test"); + r.setServiceId("testId"); + r.setTheme("theme"); + r.setDescription("description"); + r.setAttributeReleasePolicy(new ReturnAllAttributeReleasePolicy()); + + final RegisteredService r2 = this.dao.save(r); + final RegisteredService r3 = this.dao.findServiceById(r2.getId()); + + assertEquals(r, r2); + assertEquals(r2, r3); + assertNotNull(r3.getAttributeReleasePolicy()); + assertEquals(r2.getAttributeReleasePolicy(), r3.getAttributeReleasePolicy()); + } + + @Test + public void verifySaveMethodWithExistingServiceNoAttribute() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setName("test"); + r.setServiceId("testId"); + r.setTheme("theme"); + r.setDescription("description"); + + this.dao.save(r); + + final List services = this.dao.load(); + + final RegisteredService r2 = services.get(0); + + r.setId(r2.getId()); + r.setTheme("mytheme"); + + this.dao.save(r); + + final RegisteredService r3 = this.dao.findServiceById(r.getId()); + + assertEquals(r, r2); + assertEquals(r.getTheme(), r3.getTheme()); + } + +} diff --git a/cas-server-support-jdbc/src/test/java/org/jasig/cas/ticket/registry/JpaTicketRegistryTests.java b/cas-server-support-jdbc/src/test/java/org/jasig/cas/ticket/registry/JpaTicketRegistryTests.java new file mode 100644 index 000000000000..b8b1bfa11d40 --- /dev/null +++ b/cas-server-support-jdbc/src/test/java/org/jasig/cas/ticket/registry/JpaTicketRegistryTests.java @@ -0,0 +1,233 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.mock.MockService; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.jasig.cas.ticket.support.HardTimeoutExpirationPolicy; +import org.jasig.cas.ticket.support.MultiTimeUseOrTimeoutExpirationPolicy; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.annotation.IfProfileValue; +import org.springframework.test.annotation.ProfileValueSourceConfiguration; +import org.springframework.test.annotation.SystemProfileValueSource; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.jdbc.JdbcTestUtils; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.sql.DataSource; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import static org.junit.Assert.*; + + +/** + * Unit test for {@link JpaTicketRegistry} class. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:jpaTestApplicationContext.xml") +@ProfileValueSourceConfiguration(SystemProfileValueSource.class) +public class JpaTicketRegistryTests { + /** Number of clients contending for operations in concurrent test. */ + private static final int CONCURRENT_SIZE = 20; + + private static final UniqueTicketIdGenerator ID_GENERATOR = new DefaultUniqueTicketIdGenerator(64); + + private static final ExpirationPolicy EXP_POLICY_TGT = new HardTimeoutExpirationPolicy(1000); + + private static final ExpirationPolicy EXP_POLICY_ST = new MultiTimeUseOrTimeoutExpirationPolicy(1, 1000); + + /** Logger instance. */ + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private PlatformTransactionManager txManager; + + @Autowired + private JpaTicketRegistry jpaTicketRegistry; + + private JdbcTemplate simpleJdbcTemplate; + + + /** + * Set the datasource. + */ + @Autowired + public void setDataSource(final DataSource dataSource) { + this.simpleJdbcTemplate = new JdbcTemplate(dataSource); + } + + + @Before + public void setUp() { + JdbcTestUtils.deleteFromTables(simpleJdbcTemplate, "SERVICETICKET"); + JdbcTestUtils.deleteFromTables(simpleJdbcTemplate, "TICKETGRANTINGTICKET"); + } + + + @Test + public void verifyTicketCreationAndDeletion() throws Exception { + final TicketGrantingTicket newTgt = newTGT(); + addTicketInTransaction(newTgt); + final TicketGrantingTicket tgtFromDb = (TicketGrantingTicket) getTicketInTransaction(newTgt.getId()); + assertNotNull(tgtFromDb); + assertEquals(newTgt.getId(), tgtFromDb.getId()); + final ServiceTicket newSt = grantServiceTicketInTransaction(tgtFromDb); + final ServiceTicket stFromDb = (ServiceTicket) getTicketInTransaction(newSt.getId()); + assertNotNull(stFromDb); + assertEquals(newSt.getId(), stFromDb.getId()); + deleteTicketInTransaction(newTgt.getId()); + assertNull(getTicketInTransaction(newTgt.getId())); + assertNull(getTicketInTransaction(newSt.getId())); + } + + @Test + @IfProfileValue(name="cas.jpa.concurrent", value="true") + public void verifyConcurrentServiceTicketGeneration() throws Exception { + final TicketGrantingTicket newTgt = newTGT(); + addTicketInTransaction(newTgt); + final ExecutorService executor = Executors.newFixedThreadPool(CONCURRENT_SIZE); + try { + final List generators = new ArrayList<>(CONCURRENT_SIZE); + for (int i = 0; i < CONCURRENT_SIZE; i++) { + generators.add(new ServiceTicketGenerator(newTgt.getId(), this.jpaTicketRegistry, this.txManager)); + } + final List> results = executor.invokeAll(generators); + for (final Future result : results) { + assertNotNull(result.get()); + } + } catch (final Exception e) { + logger.debug("testConcurrentServiceTicketGeneration produced an error", e); + fail("testConcurrentServiceTicketGeneration failed."); + } finally { + executor.shutdownNow(); + } + } + + + static TicketGrantingTicket newTGT() { + final Principal principal = new DefaultPrincipalFactory().createPrincipal( + "bob", Collections.singletonMap("displayName", (Object) "Bob")); + return new TicketGrantingTicketImpl( + ID_GENERATOR.getNewTicketId("TGT"), + TestUtils.getAuthentication(principal), + EXP_POLICY_TGT); + } + + static ServiceTicket newST(final TicketGrantingTicket parent) { + return parent.grantServiceTicket( + ID_GENERATOR.getNewTicketId("ST"), + new MockService("https://service.example.com"), + EXP_POLICY_ST, + false); + } + + void addTicketInTransaction(final Ticket ticket) { + new TransactionTemplate(txManager).execute(new TransactionCallback() { + public Void doInTransaction(final TransactionStatus status) { + jpaTicketRegistry.addTicket(ticket); + return null; + } + }); + } + + void deleteTicketInTransaction(final String ticketId) { + new TransactionTemplate(txManager).execute(new TransactionCallback() { + public Void doInTransaction(final TransactionStatus status) { + jpaTicketRegistry.deleteTicket(ticketId); + return null; + } + }); + } + + Ticket getTicketInTransaction(final String ticketId) { + return new TransactionTemplate(txManager).execute(new TransactionCallback() { + public Ticket doInTransaction(final TransactionStatus status) { + return jpaTicketRegistry.getTicket(ticketId); + } + }); + } + + ServiceTicket grantServiceTicketInTransaction(final TicketGrantingTicket parent) { + return new TransactionTemplate(txManager).execute(new TransactionCallback() { + public ServiceTicket doInTransaction(final TransactionStatus status) { + final ServiceTicket st = newST(parent); + jpaTicketRegistry.addTicket(st); + return st; + } + }); + } + + private static class ServiceTicketGenerator implements Callable { + private PlatformTransactionManager txManager; + private final String parentTgtId; + private final JpaTicketRegistry jpaTicketRegistry; + + public ServiceTicketGenerator(final String tgtId, final JpaTicketRegistry jpaTicketRegistry, + final PlatformTransactionManager txManager) { + parentTgtId = tgtId; + this.jpaTicketRegistry = jpaTicketRegistry; + this.txManager = txManager; + } + + /** + * {@inheritDoc} + */ + @Override + public String call() throws Exception { + return new TransactionTemplate(txManager).execute(new TransactionCallback() { + public String doInTransaction(final TransactionStatus status) { + // Querying for the TGT prior to updating it as done in + // CentralAuthenticationServiceImpl#grantServiceTicket(String, Service, Credential) + final ServiceTicket st = newST((TicketGrantingTicket) jpaTicketRegistry.getTicket(parentTgtId)); + jpaTicketRegistry.addTicket(st); + return st.getId(); + } + }); + } + + } +} diff --git a/cas-server-support-jdbc/src/test/java/org/jasig/cas/ticket/registry/support/JpaLockingStrategyTests.java b/cas-server-support-jdbc/src/test/java/org/jasig/cas/ticket/registry/support/JpaLockingStrategyTests.java new file mode 100644 index 000000000000..3f53786b465b --- /dev/null +++ b/cas-server-support-jdbc/src/test/java/org/jasig/cas/ticket/registry/support/JpaLockingStrategyTests.java @@ -0,0 +1,337 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.ticket.registry.support; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.orm.jpa.SharedEntityManagerCreator; +import org.springframework.test.annotation.IfProfileValue; +import org.springframework.test.annotation.ProfileValueSourceConfiguration; +import org.springframework.test.annotation.SystemProfileValueSource; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.jdbc.JdbcTestUtils; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import static org.junit.Assert.*; + +/** + * Unit test for {@link JpaLockingStrategy}. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:jpaTestApplicationContext.xml") +@ProfileValueSourceConfiguration(SystemProfileValueSource.class) +public class JpaLockingStrategyTests implements InitializingBean { + /** Number of clients contending for lock in concurrent test. */ + private static final int CONCURRENT_SIZE = 13; + + /** Logger instance. */ + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private PlatformTransactionManager txManager; + + @Autowired + private EntityManagerFactory factory; + + private JdbcTemplate simpleJdbcTemplate; + + /** + * Set the dataSource. + */ + @Autowired + public void setDataSource(final DataSource dataSource) { + this.simpleJdbcTemplate = new JdbcTemplate(dataSource); + } + + /** + * One-time test initialization. + * + * @throws Exception On setup errors. + */ + public void afterPropertiesSet() throws Exception { + JdbcTestUtils.deleteFromTables(simpleJdbcTemplate, "locks"); + } + + /** + * Test basic acquire/release semantics. + * + * @throws Exception On errors. + */ + @Test + public void verifyAcquireAndRelease() throws Exception { + final String appId = "basic"; + final String uniqueId = appId + "-1"; + final LockingStrategy lock = newLockTxProxy(appId, uniqueId, JpaLockingStrategy.DEFAULT_LOCK_TIMEOUT); + try { + assertTrue(lock.acquire()); + assertEquals(uniqueId, getOwner(appId)); + lock.release(); + assertNull(getOwner(appId)); + } catch (final Exception e) { + logger.debug("testAcquireAndRelease produced an error", e); + fail("testAcquireAndRelease failed"); + } + } + + /** + * Test lock expiration. + * + * @throws Exception On errors. + */ + @Test + public void verifyLockExpiration() throws Exception { + final String appId = "expquick"; + final String uniqueId = appId + "-1"; + final LockingStrategy lock = newLockTxProxy(appId, uniqueId, 1); + try { + assertTrue(lock.acquire()); + assertEquals(uniqueId, getOwner(appId)); + assertFalse(lock.acquire()); + Thread.sleep(1500); + assertTrue(lock.acquire()); + assertEquals(uniqueId, getOwner(appId)); + lock.release(); + assertNull(getOwner(appId)); + } catch (final Exception e) { + logger.debug("testLockExpiration produced an error", e); + fail("testLockExpiration failed"); + } + } + + /** + * Verify non-reentrant behavior. + */ + @Test + public void verifyNonReentrantBehavior() { + final String appId = "reentrant"; + final String uniqueId = appId + "-1"; + final LockingStrategy lock = newLockTxProxy(appId, uniqueId, JpaLockingStrategy.DEFAULT_LOCK_TIMEOUT); + try { + assertTrue(lock.acquire()); + assertEquals(uniqueId, getOwner(appId)); + assertFalse(lock.acquire()); + lock.release(); + assertNull(getOwner(appId)); + } catch (final Exception e) { + logger.debug("testNonReentrantBehavior produced an error", e); + fail("testNonReentrantBehavior failed."); + } + } + + /** + * Test concurrent acquire/release semantics. + */ + @Test + @IfProfileValue(name="cas.jpa.concurrent", value="true") + public void verifyConcurrentAcquireAndRelease() throws Exception { + final ExecutorService executor = Executors.newFixedThreadPool(CONCURRENT_SIZE); + try { + testConcurrency(executor, getConcurrentLocks("concurrent-new")); + } catch (final Exception e) { + logger.debug("testConcurrentAcquireAndRelease produced an error", e); + fail("testConcurrentAcquireAndRelease failed."); + } finally { + executor.shutdownNow(); + } + } + + /** + * Test concurrent acquire/release semantics for existing lock. + */ + @Test + @IfProfileValue(name="cas.jpa.concurrent", value="true") + public void verifyConcurrentAcquireAndReleaseOnExistingLock() throws Exception { + final LockingStrategy[] locks = getConcurrentLocks("concurrent-exists"); + locks[0].acquire(); + locks[0].release(); + final ExecutorService executor = Executors.newFixedThreadPool(CONCURRENT_SIZE); + try { + testConcurrency(executor, locks); + } catch (final Exception e) { + logger.debug("testConcurrentAcquireAndReleaseOnExistingLock produced an error", e); + fail("testConcurrentAcquireAndReleaseOnExistingLock failed."); + } finally { + executor.shutdownNow(); + } + } + + private LockingStrategy[] getConcurrentLocks(final String appId) { + final LockingStrategy[] locks = new LockingStrategy[CONCURRENT_SIZE]; + for (int i = 1; i <= locks.length; i++) { + locks[i - 1] = newLockTxProxy(appId, appId + "-" + i, JpaLockingStrategy.DEFAULT_LOCK_TIMEOUT); + } + return locks; + } + + private LockingStrategy newLockTxProxy(final String appId, final String uniqueId, final int ttl) { + final JpaLockingStrategy lock = new JpaLockingStrategy(); + lock.entityManager = SharedEntityManagerCreator.createSharedEntityManager(factory); + lock.setApplicationId(appId); + lock.setUniqueId(uniqueId); + lock.setLockTimeout(ttl); + return (LockingStrategy) Proxy.newProxyInstance( + JpaLockingStrategy.class.getClassLoader(), + new Class[] {LockingStrategy.class}, + new TransactionalLockInvocationHandler(lock, this.txManager)); + } + + private String getOwner(final String appId) { + final List> results = simpleJdbcTemplate.queryForList( + "SELECT unique_id FROM locks WHERE application_id=?", appId); + if (results.size() == 0) { + return null; + } + return (String) results.get(0).get("unique_id"); + } + + private void testConcurrency(final ExecutorService executor, final LockingStrategy[] locks) throws Exception { + final List lockers = new ArrayList<>(locks.length); + for (int i = 0; i < locks.length; i++) { + lockers.add(new Locker(locks[i])); + } + + int lockCount = 0; + for (final Future result : executor.invokeAll(lockers)) { + if (result.get()) { + lockCount++; + } + } + assertTrue("Lock count should be <= 1 but was " + lockCount, lockCount <= 1); + + final List releasers = new ArrayList<>(locks.length); + for (int i = 0; i < locks.length; i++) { + releasers.add(new Releaser(locks[i])); + } + int releaseCount = 0; + for (final Future result : executor.invokeAll(lockers)) { + if (result.get()) { + releaseCount++; + } + } + assertTrue("Release count should be <= 1 but was " + releaseCount, releaseCount <= 1); + } + + private static class TransactionalLockInvocationHandler implements InvocationHandler { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final JpaLockingStrategy jpaLock; + private final PlatformTransactionManager txManager; + + public TransactionalLockInvocationHandler(final JpaLockingStrategy lock, + final PlatformTransactionManager txManager) { + jpaLock = lock; + this.txManager = txManager; + } + + public JpaLockingStrategy getLock() { + return jpaLock; + } + + /** + * {@inheritDoc} + */ + @Override + public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { + return new TransactionTemplate(txManager).execute(new TransactionCallback() { + public Object doInTransaction(final TransactionStatus status) { + try { + final Object result = method.invoke(jpaLock, args); + jpaLock.entityManager.flush(); + logger.debug("Performed {} on {}", method.getName(), jpaLock); + return result; + // Force result of transaction to database + } catch (final Exception e) { + throw new RuntimeException("Transactional method invocation failed.", e); + } + } + }); + } + + } + + private static class Locker implements Callable { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final LockingStrategy lock; + + public Locker(final LockingStrategy l) { + lock = l; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean call() throws Exception { + try { + return lock.acquire(); + } catch (final Exception e) { + logger.debug("{} failed to acquire lock", lock, e); + return false; + } + } + } + + private static class Releaser implements Callable { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final LockingStrategy lock; + + public Releaser(final LockingStrategy l) { + lock = l; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean call() throws Exception { + try { + lock.release(); + return true; + } catch (final Exception e) { + logger.debug("{} failed to release lock", lock, e); + return false; + } + } + } + +} diff --git a/cas-server-support-jdbc/src/test/resources/jpaTestApplicationContext.xml b/cas-server-support-jdbc/src/test/resources/jpaTestApplicationContext.xml new file mode 100644 index 000000000000..504e5890fd6a --- /dev/null +++ b/cas-server-support-jdbc/src/test/resources/jpaTestApplicationContext.xml @@ -0,0 +1,102 @@ + + + + + + + + + + org.hsqldb.jdbcDriver + sa + + jdbc:hsqldb:mem:cas-ticket-registry + org.hibernate.dialect.HSQLDialect + 1 + + + + + + + + org.jasig.cas.services + org.jasig.cas.ticket + org.jasig.cas.adaptors.jdbc + + + + + + ${database.dialect} + create-drop + ${database.batchSize} + + + + + + + + + + + + + + + + + + diff --git a/cas-server-support-jdbc/src/test/resources/log4j2.xml b/cas-server-support-jdbc/src/test/resources/log4j2.xml new file mode 100644 index 000000000000..a0057164e813 --- /dev/null +++ b/cas-server-support-jdbc/src/test/resources/log4j2.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-support-ldap/.gitignore b/cas-server-support-ldap/.gitignore new file mode 100644 index 000000000000..1864be3afd65 --- /dev/null +++ b/cas-server-support-ldap/.gitignore @@ -0,0 +1,2 @@ +ad.properties +openldap.properties diff --git a/cas-server-support-ldap/NOTICE b/cas-server-support-ldap/NOTICE new file mode 100644 index 000000000000..168ca9d88b1d --- /dev/null +++ b/cas-server-support-ldap/NOTICE @@ -0,0 +1,104 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS LDAP Support under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + CDI APIs under Apache License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + LDAPTIVE under Apache 2 or GNU Lesser General Public License + LDAPTIVE UNBOUNDID PROVIDER under Apache 2 or GNU Lesser General Public License + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + UnboundID LDAP SDK for Java under GNU General Public License version 2 (GPLv2) or GNU Lesser General Public License version 2.1 (LGPLv2.1) or UnboundID LDAP SDK Free Use License + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-3.4.2/cas-server-support-ldap/ldap.properties.sample b/cas-server-support-ldap/ldap.properties.sample similarity index 100% rename from cas-server-3.4.2/cas-server-support-ldap/ldap.properties.sample rename to cas-server-support-ldap/ldap.properties.sample diff --git a/cas-server-support-ldap/pom.xml b/cas-server-support-ldap/pom.xml new file mode 100644 index 000000000000..9d2042de6fc5 --- /dev/null +++ b/cas-server-support-ldap/pom.xml @@ -0,0 +1,105 @@ + + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-support-ldap + jar + Apereo CAS LDAP Support + + + + vt-middleware + https://raw.github.com/vt-middleware/maven-repo/master + + + + + + serac + Marvin S. Addison + marvin.addison@gmail.com + -5 + + developer + maintainer + + + + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + org.springframework.webflow + spring-webflow + compile + + + + org.ldaptive + ldaptive + + + + org.ldaptive + ldaptive-unboundid + ${ldaptive.version} + test + + + + org.springframework + spring-expression + + + + joda-time + joda-time + compile + + + + com.fasterxml.jackson.core + jackson-databind + compile + + + + com.unboundid + unboundid-ldapsdk + + + + + + ${project.parent.basedir} + + + diff --git a/cas-server-support-ldap/src/main/java/org/jasig/cas/adaptors/ldap/services/DefaultLdapRegisteredServiceMapper.java b/cas-server-support-ldap/src/main/java/org/jasig/cas/adaptors/ldap/services/DefaultLdapRegisteredServiceMapper.java new file mode 100644 index 000000000000..a1f56dc84493 --- /dev/null +++ b/cas-server-support-ldap/src/main/java/org/jasig/cas/adaptors/ldap/services/DefaultLdapRegisteredServiceMapper.java @@ -0,0 +1,186 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.ldap.services; + +import org.jasig.cas.services.AbstractRegisteredService; +import org.jasig.cas.services.RegexRegisteredService; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.RegisteredServiceImpl; +import org.jasig.cas.util.JsonSerializer; +import org.jasig.cas.util.LdapUtils; +import org.jasig.cas.util.services.RegisteredServiceJsonSerializer; +import org.ldaptive.LdapAttribute; +import org.ldaptive.LdapEntry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.StringUtils; + +import javax.validation.constraints.NotNull; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * Default implementation of {@link LdapRegisteredServiceMapper} that is able + * to map ldap entries to {@link RegisteredService} instances based on + * certain attributes names. This implementation also respects the object class + * attribute of LDAP entries via {@link LdapUtils#OBJECTCLASS_ATTRIBUTE}. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class DefaultLdapRegisteredServiceMapper implements LdapRegisteredServiceMapper { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultLdapRegisteredServiceMapper.class); + + @NotNull + private JsonSerializer jsonSerializer = new RegisteredServiceJsonSerializer(); + + @NotNull + private String objectClass = "casRegisteredService"; + + @NotNull + private String idAttribute = "uid"; + + @NotNull + private String serviceDefinitionAttribute = "description"; + + @Override + public LdapEntry mapFromRegisteredService(final String dn, final RegisteredService svc) { + try { + if (svc.getId() == RegisteredService.INITIAL_IDENTIFIER_VALUE) { + ((AbstractRegisteredService) svc).setId(System.nanoTime()); + } + final String newDn = getDnForRegisteredService(dn, svc); + LOGGER.debug("Creating entry {}", newDn); + + final Collection attrs = new ArrayList<>(); + attrs.add(new LdapAttribute(this.idAttribute, String.valueOf(svc.getId()))); + + final StringWriter writer = new StringWriter(); + this.jsonSerializer.toJson(writer, svc); + attrs.add(new LdapAttribute(this.serviceDefinitionAttribute, writer.toString())); + attrs.add(new LdapAttribute(LdapUtils.OBJECTCLASS_ATTRIBUTE, "top", this.objectClass)); + + return new LdapEntry(newDn, attrs); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public RegisteredService mapToRegisteredService(final LdapEntry entry) { + try { + final String value = LdapUtils.getString(entry, this.serviceDefinitionAttribute); + if (StringUtils.hasText(value)) { + final RegisteredService service = this.jsonSerializer.fromJson(value); + return service; + } + + return null; + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + public String getObjectClass() { + return this.objectClass; + } + + public void setObjectClass(final String objectClass) { + this.objectClass = objectClass; + } + + public String getIdAttribute() { + return this.idAttribute; + } + + public void setIdAttribute(final String idAttribute) { + this.idAttribute = idAttribute; + } + + public String getServiceDefinitionAttribute() { + return serviceDefinitionAttribute; + } + + public void setServiceDefinitionAttribute(final String serviceDefinitionAttribute) { + this.serviceDefinitionAttribute = serviceDefinitionAttribute; + } + + public void setJsonSerializer(final JsonSerializer jsonSerializer) { + this.jsonSerializer = jsonSerializer; + } + + @Override + public String getDnForRegisteredService(final String parentDn, final RegisteredService svc) { + return String.format("%s=%s,%s", this.idAttribute, svc.getId(), parentDn); + } + + /** + * Checks if is valid regex pattern. + * + * @param pattern the pattern + * @return true, if valid regex pattern + */ + private boolean isValidRegexPattern(final String pattern) { + try { + Pattern.compile(pattern); + } catch (final PatternSyntaxException e) { + LOGGER.debug("Failed to identify [{}] as a regular expression", pattern); + return false; + } + return true; + } + + /** + * Gets the attribute values if more than one, otherwise an empty list. + * + * @param entry the entry + * @param attrName the attr name + * @return the collection of attribute values + */ + private Collection getMultiValuedAttributeValues(@NotNull final LdapEntry entry, @NotNull final String attrName) { + final LdapAttribute attrs = entry.getAttribute(attrName); + if (attrs != null) { + return attrs.getStringValues(); + } + return Collections.emptyList(); + } + + /** + * Gets the registered service by id that would either match an ant or regex pattern. + * + * @param id the id + * @return the registered service + */ + private AbstractRegisteredService getRegisteredService(@NotNull final String id) { + if (isValidRegexPattern(id)) { + return new RegexRegisteredService(); + } + + if (new AntPathMatcher().isPattern(id)) { + return new RegisteredServiceImpl(); + } + return null; + } + +} diff --git a/cas-server-support-ldap/src/main/java/org/jasig/cas/adaptors/ldap/services/LdapRegisteredServiceMapper.java b/cas-server-support-ldap/src/main/java/org/jasig/cas/adaptors/ldap/services/LdapRegisteredServiceMapper.java new file mode 100644 index 000000000000..20d69e2e59a7 --- /dev/null +++ b/cas-server-support-ldap/src/main/java/org/jasig/cas/adaptors/ldap/services/LdapRegisteredServiceMapper.java @@ -0,0 +1,74 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.ldap.services; + +import org.jasig.cas.services.RegisteredService; +import org.ldaptive.LdapEntry; + +/** + * Strategy interface to define operations required when mapping LDAP + * entries to registered services and vice versa. + * + * @author Misagh Moayyed + * @author Marvin S. Addison + * @see DefaultLdapRegisteredServiceMapper + * @since 3.0.0 + */ +public interface LdapRegisteredServiceMapper { + + /** + * Map to registered service from ldap. + * + * @param result the result + * @return the registered service + */ + RegisteredService mapToRegisteredService(final LdapEntry result); + + /** + * Map from registered service to ldap. + * + * @param dn the dn + * @param svc the svc + * @return the ldap entry + */ + LdapEntry mapFromRegisteredService(final String dn, final RegisteredService svc); + + /** + * Gets the dn for registered service. + * + * @param parentDn the parent dn + * @param svc the svc + * @return the dn for registered service + */ + String getDnForRegisteredService(String parentDn, RegisteredService svc); + + /** + * Gets the name of the LDAP object class that represents service registry entries. + * + * @return Registered service object class. + */ + String getObjectClass(); + + /** + * Gets the name of the LDAP attribute that stores the registered service integer unique identifier. + * + * @return Registered service unique ID attribute name. + */ + String getIdAttribute(); +} diff --git a/cas-server-support-ldap/src/main/java/org/jasig/cas/adaptors/ldap/services/LdapServiceRegistryDao.java b/cas-server-support-ldap/src/main/java/org/jasig/cas/adaptors/ldap/services/LdapServiceRegistryDao.java new file mode 100644 index 000000000000..c660f7e3ac20 --- /dev/null +++ b/cas-server-support-ldap/src/main/java/org/jasig/cas/adaptors/ldap/services/LdapServiceRegistryDao.java @@ -0,0 +1,317 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.ldap.services; + +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServiceRegistryDao; +import org.jasig.cas.util.LdapUtils; +import org.ldaptive.AddOperation; +import org.ldaptive.AddRequest; +import org.ldaptive.AttributeModification; +import org.ldaptive.AttributeModificationType; +import org.ldaptive.Connection; +import org.ldaptive.ConnectionFactory; +import org.ldaptive.DeleteOperation; +import org.ldaptive.DeleteRequest; +import org.ldaptive.LdapAttribute; +import org.ldaptive.LdapEntry; +import org.ldaptive.LdapException; +import org.ldaptive.ModifyOperation; +import org.ldaptive.ModifyRequest; +import org.ldaptive.Response; +import org.ldaptive.ResultCode; +import org.ldaptive.ReturnAttributes; +import org.ldaptive.SearchFilter; +import org.ldaptive.SearchOperation; +import org.ldaptive.SearchRequest; +import org.ldaptive.SearchResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * Implementation of the ServiceRegistryDao interface which stores the services in a LDAP Directory. + * + * @author Misagh Moayyed + * @author Marvin S. Addison + * @since 4.0.0 + */ +public final class LdapServiceRegistryDao implements ServiceRegistryDao { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @NotNull + private ConnectionFactory connectionFactory; + + @NotNull + private LdapRegisteredServiceMapper ldapServiceMapper = new DefaultLdapRegisteredServiceMapper(); + + @NotNull + private String searchFilter; + + @NotNull + private String loadFilter; + + @NotNull + private SearchRequest searchRequest; + + + /** + * Inits the dao with the search filter and load filters. + */ + @PostConstruct + public void init() { + this.searchFilter = '(' + this.ldapServiceMapper.getIdAttribute() + "={0})"; + this.loadFilter = "(objectClass=" + this.ldapServiceMapper.getObjectClass() + ')'; + } + + @Override + public RegisteredService save(final RegisteredService rs) { + if (rs.getId() != RegisteredService.INITIAL_IDENTIFIER_VALUE) { + return update(rs); + } + + Connection connection = null; + try { + connection = getConnection(); + final AddOperation operation = new AddOperation(connection); + + final LdapEntry entry = this.ldapServiceMapper.mapFromRegisteredService(this.searchRequest.getBaseDn(), rs); + operation.execute(new AddRequest(entry.getDn(), entry.getAttributes())); + } catch (final LdapException e) { + logger.error(e.getMessage(), e); + } finally { + LdapUtils.closeConnection(connection); + } + return rs; + } + + /** + * Update the ldap entry with the given registered service. + * + * @param rs the rs + * @return the registered service + */ + private RegisteredService update(final RegisteredService rs) { + Connection searchConnection = null; + try { + searchConnection = getConnection(); + final Response response = searchForServiceById(searchConnection, rs.getId()); + if (hasResults(response)) { + final String currentDn = response.getResult().getEntry().getDn(); + + Connection modifyConnection = null; + try { + modifyConnection = getConnection(); + final ModifyOperation operation = new ModifyOperation(searchConnection); + + final List mods = new ArrayList<>(); + + final LdapEntry entry = this.ldapServiceMapper.mapFromRegisteredService(this.searchRequest.getBaseDn(), rs); + for (final LdapAttribute attr : entry.getAttributes()) { + if (!attr.getName().equals(this.ldapServiceMapper.getIdAttribute())) { + mods.add(new AttributeModification(AttributeModificationType.REPLACE, attr)); + } + } + final ModifyRequest request = new ModifyRequest(currentDn, mods.toArray(new AttributeModification[]{})); + operation.execute(request); + }catch (final LdapException e) { + logger.error(e.getMessage(), e); + } finally { + LdapUtils.closeConnection(modifyConnection); + } + } + } catch (final LdapException e) { + logger.error(e.getMessage(), e); + } finally { + LdapUtils.closeConnection(searchConnection); + } + return rs; + } + + @Override + public boolean delete(final RegisteredService registeredService) { + Connection connection = null; + try { + connection = getConnection(); + + final Response response = searchForServiceById(connection, registeredService.getId()); + if (hasResults(response)) { + final LdapEntry entry = response.getResult().getEntry(); + final DeleteOperation delete = new DeleteOperation(connection); + final DeleteRequest request = new DeleteRequest(entry.getDn()); + final Response res = delete.execute(request); + return res.getResultCode() == ResultCode.SUCCESS; + } + } catch (final LdapException e) { + logger.error(e.getMessage(), e); + } finally { + LdapUtils.closeConnection(connection); + } + + return false; + } + + @Override + public List load() { + Connection connection = null; + final List list = new LinkedList<>(); + try { + connection = getConnection(); + final Response response = + executeSearchOperation(connection, new SearchFilter(this.loadFilter)); + if (hasResults(response)) { + for (final LdapEntry entry : response.getResult().getEntries()) { + final RegisteredService svc = this.ldapServiceMapper.mapToRegisteredService(entry); + list.add(svc); + } + } + } catch (final LdapException e) { + logger.error(e.getMessage(), e); + } finally { + LdapUtils.closeConnection(connection); + } + return list; + } + + @Override + public RegisteredService findServiceById(final long id) { + Connection connection = null; + try { + connection = getConnection(); + + final Response response = searchForServiceById(connection, id); + if (hasResults(response)) { + return this.ldapServiceMapper.mapToRegisteredService(response.getResult().getEntry()); + } + } catch (final LdapException e) { + logger.error(e.getMessage(), e); + } finally { + LdapUtils.closeConnection(connection); + } + + return null; + } + + /** + * Search for service by id. + * + * @param connection the connection + * @param id the id + * @return the response + * @throws LdapException the ldap exception + */ + private Response searchForServiceById(final Connection connection, final long id) + throws LdapException { + + final SearchFilter filter = new SearchFilter(this.searchFilter); + filter.setParameter(0, id); + return executeSearchOperation(connection, filter); + } + + /** + * Execute search operation. + * + * @param connection the connection + * @param filter the filter + * @return the response + * @throws LdapException the ldap exception + */ + private Response executeSearchOperation(final Connection connection, final SearchFilter filter) + throws LdapException { + + final SearchOperation searchOperation = new SearchOperation(connection); + final SearchRequest request = newRequest(filter); + logger.debug("Using search request {}", request.toString()); + return searchOperation.execute(request); + } + + public void setConnectionFactory(@NotNull final ConnectionFactory factory) { + this.connectionFactory = factory; + } + + public void setLdapServiceMapper(final LdapRegisteredServiceMapper ldapServiceMapper) { + this.ldapServiceMapper = ldapServiceMapper; + } + + public void setSearchRequest(@NotNull final SearchRequest request) { + this.searchRequest = request; + } + + /** + * Checks to see if response has a result. + * + * @param response the response + * @return true, if successful + */ + private boolean hasResults(final Response response) { + final SearchResult result = response.getResult(); + if (result != null && result.getEntry() != null) { + return true; + } + + logger.trace("Requested ldap operation did not return a result or an ldap entry. Code: {}, Message: {}", + response.getResultCode(), response.getMessage()); + return false; + } + + /** + * Builds a new request. + * + * @param filter the filter + * @return the search request + */ + private SearchRequest newRequest(final SearchFilter filter) { + + final SearchRequest sr = new SearchRequest(this.searchRequest.getBaseDn(), filter); + sr.setBinaryAttributes(ReturnAttributes.ALL_USER.value()); + sr.setDerefAliases(this.searchRequest.getDerefAliases()); + sr.setSearchEntryHandlers(this.searchRequest.getSearchEntryHandlers()); + sr.setSearchReferenceHandlers(this.searchRequest.getSearchReferenceHandlers()); + sr.setFollowReferrals(this.searchRequest.getFollowReferrals()); + sr.setReturnAttributes(ReturnAttributes.ALL_USER.value()); + sr.setSearchScope(this.searchRequest.getSearchScope()); + sr.setSizeLimit(this.searchRequest.getSizeLimit()); + sr.setSortBehavior(this.searchRequest.getSortBehavior()); + sr.setTimeLimit(this.searchRequest.getTimeLimit()); + sr.setTypesOnly(this.searchRequest.getTypesOnly()); + sr.setControls(this.searchRequest.getControls()); + return sr; + } + + /** + * Gets connection from the factory. + * Opens the connection if needed. + * + * @return the connection + * @throws LdapException the ldap exception + */ + private Connection getConnection() throws LdapException { + final Connection c = this.connectionFactory.getConnection(); + if (!c.isOpen()) { + c.open(); + } + return c; + } +} diff --git a/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/LdapAuthenticationHandler.java b/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/LdapAuthenticationHandler.java new file mode 100644 index 000000000000..5a01f0a491fe --- /dev/null +++ b/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/LdapAuthenticationHandler.java @@ -0,0 +1,301 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import com.google.common.base.Functions; +import com.google.common.collect.Maps; +import org.jasig.cas.MessageDescriptor; +import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.support.LdapPasswordPolicyConfiguration; +import org.ldaptive.LdapAttribute; +import org.ldaptive.LdapEntry; +import org.ldaptive.LdapException; +import org.ldaptive.ReturnAttributes; +import org.ldaptive.auth.AuthenticationRequest; +import org.ldaptive.auth.AuthenticationResponse; +import org.ldaptive.auth.AuthenticationResultCode; +import org.ldaptive.auth.Authenticator; + +import javax.annotation.PostConstruct; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.auth.login.FailedLoginException; +import javax.security.auth.login.LoginException; +import javax.validation.constraints.NotNull; +import java.security.GeneralSecurityException; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * LDAP authentication handler that uses the ldaptive Authenticator component underneath. + * This handler provides simple attribute resolution machinery by reading attributes from the entry + * corresponding to the DN of the bound user (in the bound security context) upon successful authentication. + * Principal resolution is controlled by the following properties: + * + *
    + *
  • {@link #setPrincipalIdAttribute(String)}
  • + *
  • {@link #setPrincipalAttributeMap(java.util.Map)}
  • + *
+ * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class LdapAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler { + + /** Mapping of LDAP attribute name to principal attribute name. */ + @NotNull + protected Map principalAttributeMap = Collections.emptyMap(); + + /** List of additional attributes to be fetched but are not principal attributes. */ + @NotNull + protected List additionalAttributes = Collections.emptyList(); + + /** + * Performs LDAP authentication given username/password. + **/ + @NotNull + private final Authenticator authenticator; + + /** Component name. */ + @NotNull + private String name = LdapAuthenticationHandler.class.getSimpleName(); + + /** Name of attribute to be used for resolved principal. */ + private String principalIdAttribute; + + /** Flag indicating whether multiple values are allowed fo principalIdAttribute. */ + private boolean allowMultiplePrincipalAttributeValues; + + /** Set of LDAP attributes fetch from an entry as part of the authentication process. */ + private String[] authenticatedEntryAttributes = ReturnAttributes.NONE.value(); + + /** + * Creates a new authentication handler that delegates to the given authenticator. + * + * @param authenticator Ldaptive authenticator component. + */ + public LdapAuthenticationHandler(@NotNull final Authenticator authenticator) { + this.authenticator = authenticator; + } + + /** + * Sets the component name. Defaults to simple class name. + * + * @param name Authentication handler name. + */ + public void setName(final String name) { + this.name = name; + } + + /** + * Sets the name of the LDAP principal attribute whose value should be used for the + * principal ID. + * + * @param attributeName LDAP attribute name. + */ + public void setPrincipalIdAttribute(final String attributeName) { + this.principalIdAttribute = attributeName; + } + + /** + * Sets a flag that determines whether multiple values are allowed for the {@link #principalIdAttribute}. + * This flag only has an effect if {@link #principalIdAttribute} is configured. If multiple values are detected + * when the flag is false, the first value is used and a warning is logged. If multiple values are detected + * when the flag is true, an exception is raised. + * + * @param allowed True to allow multiple principal ID attribute values, false otherwise. + */ + public void setAllowMultiplePrincipalAttributeValues(final boolean allowed) { + this.allowMultiplePrincipalAttributeValues = allowed; + } + + /** + * Sets the mapping of additional principal attributes where the key is the LDAP attribute + * name and the value is the principal attribute name. The key set defines the set of + * attributes read from the LDAP entry at authentication time. Note that the principal ID attribute + * should not be listed among these attributes. + * + * @param attributeNameMap Map of LDAP attribute name to principal attribute name. + */ + public void setPrincipalAttributeMap(final Map attributeNameMap) { + this.principalAttributeMap = attributeNameMap; + } + + /** + * Sets the mapping of additional principal attributes where the key and value is the LDAP attribute + * name. Note that the principal ID attribute + * should not be listed among these attributes. + * + * @param attributeList List of LDAP attribute names + */ + public void setPrincipalAttributeList(final List attributeList) { + this.principalAttributeMap = Maps.uniqueIndex(attributeList, Functions.toStringFunction()); + } + + /** + * Sets the list of additional attributes to be fetched from the user entry during authentication. + * These attributes are not bound to the principal. + *

+ * A common use case for these attributes is to support password policy machinery. + * + * @param additionalAttributes List of operational attributes to fetch when resolving an entry. + */ + public void setAdditionalAttributes(final List additionalAttributes) { + this.additionalAttributes = additionalAttributes; + } + + @Override + protected HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential upc) + throws GeneralSecurityException, PreventedException { + final AuthenticationResponse response; + try { + logger.debug("Attempting LDAP authentication for {}", upc); + final String password = getPasswordEncoder().encode(upc.getPassword()); + final AuthenticationRequest request = new AuthenticationRequest(upc.getUsername(), + new org.ldaptive.Credential(password), + this.authenticatedEntryAttributes); + response = this.authenticator.authenticate(request); + } catch (final LdapException e) { + throw new PreventedException("Unexpected LDAP error", e); + } + logger.debug("LDAP response: {}", response); + + final List messageList; + + final LdapPasswordPolicyConfiguration ldapPasswordPolicyConfiguration = + (LdapPasswordPolicyConfiguration) super.getPasswordPolicyConfiguration(); + if (ldapPasswordPolicyConfiguration != null) { + logger.debug("Applying password policy to {}", response); + messageList = ldapPasswordPolicyConfiguration.getAccountStateHandler().handle( + response, ldapPasswordPolicyConfiguration); + } else { + messageList = Collections.emptyList(); + } + + if (response.getResult()) { + return createHandlerResult(upc, createPrincipal(upc.getUsername(), response.getLdapEntry()), messageList); + } + + if (AuthenticationResultCode.DN_RESOLUTION_FAILURE == response.getAuthenticationResultCode()) { + throw new AccountNotFoundException(upc.getUsername() + " not found."); + } + throw new FailedLoginException("Invalid credentials"); + } + + /** + * Examine account state to see if any errors are present. + * If so, throws the relevant security exception. + * + * @param response the response + * @throws LoginException the login exception + */ + /** + * Handle post authentication processing. + * + * @param credential the credential + * @return the handler result + */ + @Override + public boolean supports(final Credential credential) { + return credential instanceof UsernamePasswordCredential; + } + + @Override + public String getName() { + return this.name; + } + + /** + * Creates a CAS principal with attributes if the LDAP entry contains principal attributes. + * + * @param username Username that was successfully authenticated which is used for principal ID when + * {@link #setPrincipalIdAttribute(String)} is not specified. + * @param ldapEntry LDAP entry that may contain principal attributes. + * + * @return Principal if the LDAP entry contains at least a principal ID attribute value, null otherwise. + * + * @throws LoginException On security policy errors related to principal creation. + */ + protected Principal createPrincipal(final String username, final LdapEntry ldapEntry) throws LoginException { + final String id; + if (this.principalIdAttribute != null) { + final LdapAttribute principalAttr = ldapEntry.getAttribute(this.principalIdAttribute); + if (principalAttr == null || principalAttr.size() == 0) { + throw new LoginException(this.principalIdAttribute + " attribute not found for " + username); + } + if (principalAttr.size() > 1) { + if (this.allowMultiplePrincipalAttributeValues) { + logger.warn( + "Found multiple values for principal ID attribute: {}. Using first value={}.", + principalAttr, + principalAttr.getStringValue()); + } else { + throw new LoginException("Multiple principal values not allowed: " + principalAttr); + } + } + id = principalAttr.getStringValue(); + } else { + id = username; + } + final Map attributeMap = new LinkedHashMap<>(this.principalAttributeMap.size()); + for (final Map.Entry ldapAttr : this.principalAttributeMap.entrySet()) { + final LdapAttribute attr = ldapEntry.getAttribute(ldapAttr.getKey()); + if (attr != null) { + logger.debug("Found principal attribute: {}", attr); + final String principalAttrName = ldapAttr.getValue(); + if (attr.size() > 1) { + attributeMap.put(principalAttrName, attr.getStringValues()); + } else { + attributeMap.put(principalAttrName, attr.getStringValue()); + } + } + } + return this.principalFactory.createPrincipal(id, attributeMap); + } + + /** + * Initialize the handler, setup the authentication entry attributes. + */ + @PostConstruct + public void initialize() { + /** + * Use a set to ensure we ignore duplicates. + */ + final Set attributes = new HashSet<>(); + + if (this.principalIdAttribute != null) { + attributes.add(this.principalIdAttribute); + } + if (!this.principalAttributeMap.isEmpty()) { + attributes.addAll(this.principalAttributeMap.keySet()); + } + if (!this.additionalAttributes.isEmpty()) { + attributes.addAll(this.additionalAttributes); + } + if (!attributes.isEmpty()) { + this.authenticatedEntryAttributes = attributes.toArray(new String[attributes.size()]); + } + } + + +} diff --git a/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/support/AccountStateHandler.java b/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/support/AccountStateHandler.java new file mode 100644 index 000000000000..79d3941504c0 --- /dev/null +++ b/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/support/AccountStateHandler.java @@ -0,0 +1,47 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.support; + +import java.util.List; +import javax.security.auth.login.LoginException; + +import org.jasig.cas.MessageDescriptor; +import org.ldaptive.auth.AuthenticationResponse; + +/** + * Strategy pattern for handling directory-specific account state data. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public interface AccountStateHandler { + /** + * Handles the account state producing an error or warning messages as appropriate to the state. + * + * @param response LDAP authentication response containing attributes, response controls, and account state that + * can be used to determine user account state. + * @param configuration Password policy configuration. + * + * @return List of warning messages. + * + * @throws LoginException When account state causes authentication failure. + */ + List handle(AuthenticationResponse response, LdapPasswordPolicyConfiguration configuration) + throws LoginException; +} diff --git a/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/support/DefaultAccountStateHandler.java b/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/support/DefaultAccountStateHandler.java new file mode 100644 index 000000000000..d25e23195f3b --- /dev/null +++ b/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/support/DefaultAccountStateHandler.java @@ -0,0 +1,171 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.support; + +import org.jasig.cas.DefaultMessageDescriptor; +import org.jasig.cas.MessageDescriptor; +import org.jasig.cas.authentication.AccountDisabledException; +import org.jasig.cas.authentication.AccountPasswordMustChangeException; +import org.jasig.cas.authentication.InvalidLoginLocationException; +import org.jasig.cas.authentication.InvalidLoginTimeException; +import org.joda.time.Days; +import org.joda.time.Instant; +import org.ldaptive.auth.AccountState; +import org.ldaptive.auth.AuthenticationResponse; +import org.ldaptive.auth.ext.ActiveDirectoryAccountState; +import org.ldaptive.auth.ext.EDirectoryAccountState; +import org.ldaptive.auth.ext.PasswordExpirationAccountState; +import org.ldaptive.control.PasswordPolicyControl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.security.auth.login.AccountExpiredException; +import javax.security.auth.login.AccountLockedException; +import javax.security.auth.login.CredentialExpiredException; +import javax.security.auth.login.LoginException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Default account state handler. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class DefaultAccountStateHandler implements AccountStateHandler { + /** Map of account state error to CAS authentication exception. */ + protected final Map errorMap; + + /** Logger instance. */ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + /** + * Instantiates a new account state handler, that populates + * the error map with LDAP error codes and corresponding exceptions. + */ + public DefaultAccountStateHandler() { + this.errorMap = new HashMap<>(); + this.errorMap.put(ActiveDirectoryAccountState.Error.ACCOUNT_DISABLED, new AccountDisabledException()); + this.errorMap.put(ActiveDirectoryAccountState.Error.ACCOUNT_LOCKED_OUT, new AccountLockedException()); + this.errorMap.put(ActiveDirectoryAccountState.Error.INVALID_LOGON_HOURS, new InvalidLoginTimeException()); + this.errorMap.put(ActiveDirectoryAccountState.Error.INVALID_WORKSTATION, new InvalidLoginLocationException()); + this.errorMap.put(ActiveDirectoryAccountState.Error.PASSWORD_MUST_CHANGE, new AccountPasswordMustChangeException()); + this.errorMap.put(ActiveDirectoryAccountState.Error.PASSWORD_EXPIRED, new CredentialExpiredException()); + this.errorMap.put(EDirectoryAccountState.Error.ACCOUNT_EXPIRED, new AccountExpiredException()); + this.errorMap.put(EDirectoryAccountState.Error.LOGIN_LOCKOUT, new AccountLockedException()); + this.errorMap.put(EDirectoryAccountState.Error.LOGIN_TIME_LIMITED, new InvalidLoginTimeException()); + this.errorMap.put(EDirectoryAccountState.Error.PASSWORD_EXPIRED, new CredentialExpiredException()); + this.errorMap.put(PasswordExpirationAccountState.Error.PASSWORD_EXPIRED, new CredentialExpiredException()); + this.errorMap.put(PasswordPolicyControl.Error.ACCOUNT_LOCKED, new AccountLockedException()); + this.errorMap.put(PasswordPolicyControl.Error.PASSWORD_EXPIRED, new CredentialExpiredException()); + this.errorMap.put(PasswordPolicyControl.Error.CHANGE_AFTER_RESET, new CredentialExpiredException()); + } + + @Override + public List handle(final AuthenticationResponse response, final LdapPasswordPolicyConfiguration configuration) + throws LoginException { + + final AccountState state = response.getAccountState(); + if (state == null) { + logger.debug("Account state not defined. Returning empty list of messages."); + return Collections.emptyList(); + } + final List messages = new ArrayList<>(); + handleError(state.getError(), response, configuration, messages); + handleWarning(state.getWarning(), response, configuration, messages); + + return messages; + } + + /** + * Handle an account state error produced by ldaptive account state machinery. + *

+ * Override this method to provide custom error handling. + * + * @param error Account state error. + * @param response Ldaptive authentication response. + * @param configuration Password policy configuration. + * @param messages Container for messages produced by account state error handling. + * + * @throws LoginException On errors that should be communicated as login exceptions. + */ + protected void handleError( + final AccountState.Error error, + final AuthenticationResponse response, + final LdapPasswordPolicyConfiguration configuration, + final List messages) + throws LoginException { + + logger.debug("Handling error {}", error); + final LoginException ex = this.errorMap.get(error); + if (ex != null) { + throw ex; + } + logger.debug("No LDAP error mapping defined for {}", error); + } + + + /** + * Handle an account state warning produced by ldaptive account state machinery. + *

+ * Override this method to provide custom warning message handling. + * + * @param warning the account state warning messages. + * @param response Ldaptive authentication response. + * @param configuration Password policy configuration. + * @param messages Container for messages produced by account state warning handling. + */ + protected void handleWarning( + final AccountState.Warning warning, + final AuthenticationResponse response, + final LdapPasswordPolicyConfiguration configuration, + final List messages) { + + logger.debug("Handling warning {}", warning); + if (warning == null) { + logger.debug("Account state warning not defined"); + return; + } + + final Calendar expDate = warning.getExpiration(); + final Days ttl = Days.daysBetween(Instant.now(), new Instant(expDate)); + logger.debug( + "Password expires in {} days. Expiration warning threshold is {} days.", + ttl.getDays(), + configuration.getPasswordWarningNumberOfDays()); + if (configuration.isAlwaysDisplayPasswordExpirationWarning() + || ttl.getDays() < configuration.getPasswordWarningNumberOfDays()) { + messages.add(new PasswordExpiringWarningMessageDescriptor( + "Password expires in {0} days. Please change your password at {1}", + ttl.getDays(), + configuration.getPasswordPolicyUrl())); + } + if (warning.getLoginsRemaining() > 0) { + messages.add(new DefaultMessageDescriptor( + "password.expiration.loginsRemaining", + "You have {0} logins remaining before you MUST change your password.", + warning.getLoginsRemaining())); + + } + } +} diff --git a/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/support/LdapPasswordPolicyConfiguration.java b/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/support/LdapPasswordPolicyConfiguration.java new file mode 100644 index 000000000000..49b4e12d0a75 --- /dev/null +++ b/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/support/LdapPasswordPolicyConfiguration.java @@ -0,0 +1,52 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.support; + +import javax.validation.constraints.NotNull; + +/** + * LDAP-specific password policy configuration container. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class LdapPasswordPolicyConfiguration extends PasswordPolicyConfiguration { + + /** Directory-specific account state handler component. */ + @NotNull + private AccountStateHandler accountStateHandler; + + + /** + * @return Account state handler component. + */ + public AccountStateHandler getAccountStateHandler() { + return accountStateHandler; + } + + /** + * Sets the directory-specific account state handler. If none is defined, account state handling is disabled, + * which is the default behavior. + * + * @param accountStateHandler Account state handler. + */ + public void setAccountStateHandler(final AccountStateHandler accountStateHandler) { + this.accountStateHandler = accountStateHandler; + } +} diff --git a/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/support/OptionalWarningAccountStateHandler.java b/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/support/OptionalWarningAccountStateHandler.java new file mode 100644 index 000000000000..6c5c9605e0ed --- /dev/null +++ b/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/support/OptionalWarningAccountStateHandler.java @@ -0,0 +1,110 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.support; + +import java.util.List; + +import javax.validation.constraints.NotNull; + +import org.jasig.cas.MessageDescriptor; +import org.ldaptive.LdapAttribute; +import org.ldaptive.auth.AccountState; +import org.ldaptive.auth.AuthenticationResponse; + +/** + * The component supports both opt-in and opt-out warnings on a per-user basis using a simple algorithm of three + * variables: + *

    + *
  1. {@link #setWarningAttributeName(String) warningAttributeName}
  2. + *
  3. {@link #setWarningAttributeValue(String)} warningAttributeValue}
  4. + *
  5. {@link #setDisplayWarningOnMatch(boolean) displayWarningOnMatch}
  6. + *
+ * The first two parameters define an attribute on the user entry to match on, and the third parameter determines + * whether password expiration warnings should be displayed on match. + *

+ * Deployers MUST configure LDAP components to provide warningAttributeName in the set of attributes + * returned from the LDAP query for user details. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class OptionalWarningAccountStateHandler extends DefaultAccountStateHandler { + + /** Name of user attribute that describes whether or not to display expiration warnings. */ + @NotNull + private String warningAttributeName; + + /** Attribute value to match. */ + @NotNull + private String warningAttributeValue; + + /** + * True to opt into password expiration + * warnings on match, false to opt out on match. + **/ + private boolean displayWarningOnMatch = true; + + + /** + * Sets the user attribute used to determine whether to display password expiration warnings. + * + * @param warningAttributeName Attribute on authenticated user entry. + */ + public void setWarningAttributeName(final String warningAttributeName) { + this.warningAttributeName = warningAttributeName; + } + + /** + * Sets the value of {@link #warningAttributeName} used as basis of comparison. + * + * @param warningAttributeValue Value to match against. + */ + public void setWarningAttributeValue(final String warningAttributeValue) { + this.warningAttributeValue = warningAttributeValue; + } + + /** + * Determines whether password expiration warnings are opt-in or opt-out. + * + * @param displayWarningOnMatch True to opt into password expiration warnings on match, false to opt out on match. + * Default is true. + */ + public void setDisplayWarningOnMatch(final boolean displayWarningOnMatch) { + this.displayWarningOnMatch = displayWarningOnMatch; + } + + @Override + protected void handleWarning( + final AccountState.Warning warning, + final AuthenticationResponse response, + final LdapPasswordPolicyConfiguration configuration, + final List messages) { + + final LdapAttribute attribute = response.getLdapEntry().getAttribute(this.warningAttributeName); + boolean matches = false; + if (attribute != null) { + logger.debug("Found warning attribute {} with value {}", attribute.getName(), attribute.getStringValue()); + matches = this.warningAttributeValue.equals(attribute.getStringValue()); + } + logger.debug("matches={}, displayWarningOnMatch={}", matches, displayWarningOnMatch); + if (displayWarningOnMatch == matches) { + super.handleWarning(warning, response, configuration, messages); + } + } +} diff --git a/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/support/UpnSearchEntryResolver.java b/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/support/UpnSearchEntryResolver.java new file mode 100644 index 000000000000..b1bc6d0f8a7f --- /dev/null +++ b/cas-server-support-ldap/src/main/java/org/jasig/cas/authentication/support/UpnSearchEntryResolver.java @@ -0,0 +1,85 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.support; + +import org.ldaptive.SearchFilter; +import org.ldaptive.SearchRequest; +import org.ldaptive.SearchScope; +import org.ldaptive.auth.AuthenticationCriteria; +import org.ldaptive.auth.SearchEntryResolver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @deprecated As of 4.1, this component is no longer required. Ldaptive's own {@link SearchEntryResolver} now supports + * all the functionality that is presented by this class through various settings and filter parameters. This class + * is scheduled to be removed in future CAS versions. + * + * Ldaptive extension component for Active Directory that supports querying for an entry by User Principal Name (UPN). + * This component only provides meaningful results when used on a bound connection; therefore it cannot be used with + * ldaptive support for the AD FastBind operation, org.ldaptive.ad.extended.FastBindOperation. + *

+ * Since the UPN is abstracted from the location of an entry in the directory, subtree searching is required to + * locate an entry. The {@link #setBaseDn(String)} property must be set to the lowest common branch where all + * authenticated users are located, commonly dc=example,dc=org or OU=Users,dc=example,dc=org. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +@Deprecated +public final class UpnSearchEntryResolver extends SearchEntryResolver { + + private static final Logger LOGGER = LoggerFactory.getLogger(UpnSearchEntryResolver.class); + + /** UPN-based search filter. */ + private static final String SEARCH_FILTER = "userPrincipalName={0}"; + + /** Base DN of LDAP subtree search. */ + private String baseDn; + + /** + * Instantiates a new Search entry resolver. + */ + public UpnSearchEntryResolver() { + super(); + LOGGER.warn("UpnSearchEntryResolver will be removed in future CAS versions. Consider using the SearchEntryResolver directly"); + } + /** + * Sets the base DN used for the subtree search for LDAP entry. + * + * @param dn Subtree search base DN. + */ + public void setBaseDn(final String dn) { + this.baseDn = dn; + } + + /** + * {@inheritDoc} + */ + @Override + protected SearchRequest createSearchRequest(final AuthenticationCriteria ac) { + final SearchRequest sr = new SearchRequest(); + sr.setSearchScope(SearchScope.SUBTREE); + sr.setBaseDn(this.baseDn); + sr.setSearchFilter(new SearchFilter(SEARCH_FILTER, new Object[]{ac.getDn()})); + sr.setSearchEntryHandlers(getSearchEntryHandlers()); + sr.setReturnAttributes(ac.getAuthenticationRequest().getReturnAttributes()); + return sr; + } +} diff --git a/cas-server-support-ldap/src/main/java/org/jasig/cas/monitor/ConnectionFactoryMonitor.java b/cas-server-support-ldap/src/main/java/org/jasig/cas/monitor/ConnectionFactoryMonitor.java new file mode 100644 index 000000000000..7451a3d5f055 --- /dev/null +++ b/cas-server-support-ldap/src/main/java/org/jasig/cas/monitor/ConnectionFactoryMonitor.java @@ -0,0 +1,82 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import org.jasig.cas.util.LdapUtils; +import org.ldaptive.Connection; +import org.ldaptive.ConnectionFactory; +import org.ldaptive.LdapException; +import org.ldaptive.pool.Validator; + +/** + * Monitors an ldaptive {@link ConnectionFactory}. While this class can be used with instances of + * {@link org.ldaptive.pool.PooledConnectionFactory}, the {@link PooledConnectionFactoryMonitor} class is preferable. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class ConnectionFactoryMonitor extends AbstractNamedMonitor { + + /** OK status. */ + private static final Status OK = new Status(StatusCode.OK); + + /** Error status. */ + private static final Status ERROR = new Status(StatusCode.ERROR); + + /** Source of connections to validate. */ + private final ConnectionFactory connectionFactory; + + /** Connection validator. */ + private final Validator validator; + + + /** + * Creates a new instance that monitors the given connection factory. + * + * @param factory Connection factory to monitor. + * @param validator Validates connections from the factory. + */ + public ConnectionFactoryMonitor(final ConnectionFactory factory, final Validator validator) { + this.connectionFactory = factory; + this.validator = validator; + } + + + /** + * Gets a connection from the underlying connection factory and attempts to validate it. + * + * @return Status with code {@link StatusCode#OK} on success otherwise {@link StatusCode#ERROR}. + */ + @Override + public Status observe() { + Connection conn = null; + try { + conn = this.connectionFactory.getConnection(); + if (!conn.isOpen()) { + conn.open(); + } + return this.validator.validate(conn) ? OK : ERROR; + } catch (final LdapException e) { + logger.warn("Validation failed with error.", e); + } finally { + LdapUtils.closeConnection(conn); + } + return ERROR; + } +} diff --git a/cas-server-support-ldap/src/main/java/org/jasig/cas/monitor/PooledConnectionFactoryMonitor.java b/cas-server-support-ldap/src/main/java/org/jasig/cas/monitor/PooledConnectionFactoryMonitor.java new file mode 100644 index 000000000000..310a793030e1 --- /dev/null +++ b/cas-server-support-ldap/src/main/java/org/jasig/cas/monitor/PooledConnectionFactoryMonitor.java @@ -0,0 +1,73 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import org.jasig.cas.util.LdapUtils; +import org.ldaptive.Connection; +import org.ldaptive.pool.PooledConnectionFactory; +import org.ldaptive.pool.Validator; + +/** + * Monitors an ldaptive {@link PooledConnectionFactory}. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class PooledConnectionFactoryMonitor extends AbstractPoolMonitor { + + /** Source of connections to validate. */ + private final PooledConnectionFactory connectionFactory; + + /** Connection validator. */ + private final Validator validator; + + + /** + * Creates a new instance that monitors the given pooled connection factory. + * + * @param factory Connection factory to monitor. + * @param validator Validates connections from the factory. + */ + public PooledConnectionFactoryMonitor( + final PooledConnectionFactory factory, final Validator validator) { + this.connectionFactory = factory; + this.validator = validator; + } + + + @Override + protected StatusCode checkPool() throws Exception { + final Connection conn = this.connectionFactory.getConnection(); + try { + return this.validator.validate(conn) ? StatusCode.OK : StatusCode.ERROR; + } finally { + LdapUtils.closeConnection(conn); + } + } + + @Override + protected int getIdleCount() { + return this.connectionFactory.getConnectionPool().availableCount(); + } + + @Override + protected int getActiveCount() { + return this.connectionFactory.getConnectionPool().activeCount(); + } +} diff --git a/cas-server-support-ldap/src/main/java/org/jasig/cas/persondir/LdapPersonAttributeDao.java b/cas-server-support-ldap/src/main/java/org/jasig/cas/persondir/LdapPersonAttributeDao.java new file mode 100644 index 000000000000..f8b6056f5dd6 --- /dev/null +++ b/cas-server-support-ldap/src/main/java/org/jasig/cas/persondir/LdapPersonAttributeDao.java @@ -0,0 +1,222 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.persondir; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.naming.directory.SearchControls; +import javax.validation.constraints.NotNull; + +import org.jasig.cas.util.LdapUtils; +import org.jasig.services.persondir.IPersonAttributes; +import org.jasig.services.persondir.support.AbstractQueryPersonAttributeDao; +import org.jasig.services.persondir.support.CaseInsensitiveAttributeNamedPersonImpl; +import org.jasig.services.persondir.support.CaseInsensitiveNamedPersonImpl; +import org.ldaptive.Connection; +import org.ldaptive.ConnectionFactory; +import org.ldaptive.LdapAttribute; +import org.ldaptive.LdapEntry; +import org.ldaptive.LdapException; +import org.ldaptive.Response; +import org.ldaptive.SearchFilter; +import org.ldaptive.SearchOperation; +import org.ldaptive.SearchRequest; +import org.ldaptive.SearchResult; +import org.ldaptive.SearchScope; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @deprecated As of 4.1. Use {@link org.jasig.services.persondir.support.ldap.LdaptivePersonAttributeDao}. + * + * Person directory IPersonAttribute implementation that queries an LDAP directory + * with ldaptive components to populate person attributes. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +@Deprecated +public class LdapPersonAttributeDao extends AbstractQueryPersonAttributeDao { + + /** Logger instance. **/ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** Search base DN. */ + @NotNull + private String baseDN; + + /** Search controls. */ + @NotNull + private SearchControls searchControls; + + /** LDAP connection factory. */ + @NotNull + private ConnectionFactory connectionFactory; + + /** LDAP search scope. */ + private SearchScope searchScope; + + /** LDAP search filter. */ + @NotNull + private String searchFilter; + + /** LDAP attributes to fetch from search results. */ + private String[] attributes; + + /** + * Sets the base DN of the LDAP search for attributes. + * + * @param dn LDAP base DN of search. + */ + public void setBaseDN(final String dn) { + this.baseDN = dn; + } + + /** + * Sets the LDAP search filter used to query for person attributes. + * + * @param filter Search filter of the form "(usernameAttribute={0})" where {0} and similar ordinal placeholders + * are replaced with query parameters. + */ + public void setSearchFilter(final String filter) { + this.searchFilter = filter; + } + + /** + * Sets a number of parameters that control LDAP search semantics including search scope, + * maximum number of results retrieved, and search timeout. + * + * @param searchControls LDAP search controls. + */ + public void setSearchControls(final SearchControls searchControls) { + this.searchControls = searchControls; + } + + /** + * Sets the connection factory that produces LDAP connections on which searches occur. It is strongly recommended + * that this be a PooledConnecitonFactory object. + * + * @param connectionFactory LDAP connection factory. + */ + public void setConnectionFactory(final ConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + /** + * Initializes the object after properties are set. + */ + @PostConstruct + public void initialize() { + for (final SearchScope scope : SearchScope.values()) { + if (scope.ordinal() == this.searchControls.getSearchScope()) { + this.searchScope = scope; + } + } + this.attributes = getResultAttributeMapping().keySet().toArray(new String[getResultAttributeMapping().size()]); + } + + @Override + protected List getPeopleForQuery(final SearchFilter filter, final String userName) { + Connection connection = null; + try { + try { + connection = this.connectionFactory.getConnection(); + connection.open(); + } catch (final LdapException e) { + throw new RuntimeException("Failed getting LDAP connection", e); + } + final Response response; + try { + response = new SearchOperation(connection).execute(createRequest(filter)); + } catch (final LdapException e) { + throw new RuntimeException("Failed executing LDAP query " + filter, e); + } + final SearchResult result = response.getResult(); + final List peopleAttributes = new ArrayList<>(result.size()); + for (final LdapEntry entry : result.getEntries()) { + final IPersonAttributes person; + final String userNameAttribute = this.getConfiguredUserNameAttribute(); + final Map> attributes = convertLdapEntryToMap(entry); + if (attributes.containsKey(userNameAttribute)) { + person = new CaseInsensitiveAttributeNamedPersonImpl(userNameAttribute, attributes); + } else { + person = new CaseInsensitiveNamedPersonImpl(userName , attributes); + } + peopleAttributes.add(person); + } + + return peopleAttributes; + } finally { + LdapUtils.closeConnection(connection); + } + } + + @Override + protected SearchFilter appendAttributeToQuery( + final SearchFilter filter, final String attribute, final List values) { + final SearchFilter query; + if (filter == null && values.size() > 0) { + query = new SearchFilter(this.searchFilter); + query.setParameter(0, values.get(0).toString()); + logger.debug("Constructed LDAP search query [{}]", query.format()); + } else { + throw new UnsupportedOperationException("Multiple attributes not supported."); + } + return query; + } + + /** + * Creates a search request from a search filter. + * + * @param filter LDAP search filter. + * + * @return ldaptive search request. + */ + private SearchRequest createRequest(final SearchFilter filter) { + final SearchRequest request = new SearchRequest(); + request.setBaseDn(this.baseDN); + request.setSearchFilter(filter); + request.setReturnAttributes(this.attributes); + request.setSearchScope(this.searchScope); + request.setSizeLimit(this.searchControls.getCountLimit()); + request.setTimeLimit(this.searchControls.getTimeLimit()); + return request; + } + + /** + * Converts an ldaptive LdapEntry containing result entry attributes into an attribute map as needed + * by Person Directory components. + * + * @param entry Ldap entry. + * + * @return Attribute map. + */ + private Map> convertLdapEntryToMap(final LdapEntry entry) { + final Map> attributeMap = new LinkedHashMap<>(entry.size()); + for (final LdapAttribute attr : entry.getAttributes()) { + attributeMap.put(attr.getName(), new ArrayList(attr.getStringValues())); + } + logger.debug("Converted ldap DN entry [{}] to attribute map {}", entry.getDn(), attributeMap.toString()); + return attributeMap; + } +} diff --git a/cas-server-support-ldap/src/main/java/org/jasig/cas/userdetails/LdapUserDetailsService.java b/cas-server-support-ldap/src/main/java/org/jasig/cas/userdetails/LdapUserDetailsService.java new file mode 100644 index 000000000000..ffa7f1fdd587 --- /dev/null +++ b/cas-server-support-ldap/src/main/java/org/jasig/cas/userdetails/LdapUserDetailsService.java @@ -0,0 +1,217 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.userdetails; + +import org.ldaptive.ConnectionFactory; +import org.ldaptive.LdapAttribute; +import org.ldaptive.LdapEntry; +import org.ldaptive.LdapException; +import org.ldaptive.Response; +import org.ldaptive.SearchExecutor; +import org.ldaptive.SearchFilter; +import org.ldaptive.SearchResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.Collection; + +/** + * Provides a simple {@link UserDetailsService} implementation that obtains user details from an LDAP search. + * Two searches are performed by this component for every user details lookup: + * + *
    + *
  1. Search for an entry to resolve the username. In most cases the search should return exactly one result, + * but the {@link #setAllowMultipleResults(boolean)} property may be toggled to change that behavior.
  2. + *
  3. Search for groups of which the user is a member. This search commonly occurs on a separate directory + * branch than that of the user search.
  4. + *
+ * + * @author Marvin S. Addison + * @author Misagh Moayyed + * @since 4.0.0 + */ +public class LdapUserDetailsService implements UserDetailsService { + + /** Default role prefix. */ + public static final String DEFAULT_ROLE_PREFIX = "ROLE_"; + + /** Placeholder for unknown password given to user details. */ + public static final String UNKNOWN_PASSWORD = ""; + + /** Logger instance. */ + private final Logger logger = LoggerFactory.getLogger(getClass()); + + /** Source of LDAP connections. */ + @NotNull + private final ConnectionFactory connectionFactory; + + /** Executes the search query for user data. */ + @NotNull + private final SearchExecutor userSearchExecutor; + + /** Executes the search query for roles. */ + @NotNull + private final SearchExecutor roleSearchExecutor; + + /** Specify the name of LDAP attribute to use as principal identifier. */ + @NotNull + private final String userAttributeName; + + /** Specify the name of LDAP attribute to be used as the basis for role granted authorities. */ + @NotNull + private final String roleAttributeName; + + /** Prefix appended to the uppercased + * {@link #roleAttributeName} per the normal Spring Security convention. + **/ + @NotNull + private String rolePrefix = DEFAULT_ROLE_PREFIX; + + /** Flag that indicates whether multiple search results are allowed for a given credential. */ + private boolean allowMultipleResults; + + /** + * Creates a new instance with the given required parameters. + * + * @param factory Source of LDAP connections for searches. + * @param userSearchExecutor Executes the LDAP search for user data. + * @param roleSearchExecutor Executes the LDAP search for role data. + * @param userAttributeName Name of LDAP attribute that contains username for user details. + * @param roleAttributeName Name of LDAP attribute that contains role membership data for the user. + */ + public LdapUserDetailsService( + final ConnectionFactory factory, + final SearchExecutor userSearchExecutor, + final SearchExecutor roleSearchExecutor, + final String userAttributeName, + final String roleAttributeName) { + + this.connectionFactory = factory; + this.userSearchExecutor = userSearchExecutor; + this.roleSearchExecutor = roleSearchExecutor; + this.userAttributeName = userAttributeName; + this.roleAttributeName = roleAttributeName; + } + + + /** + * Sets the prefix appended to the uppercase {@link #roleAttributeName} per the normal Spring Security convention. + * The default value {@value #DEFAULT_ROLE_PREFIX} is sufficient in most cases. + * + * @param rolePrefix Role prefix. + */ + public void setRolePrefix(final String rolePrefix) { + this.rolePrefix = rolePrefix; + } + + + /** + * Sets whether to allow multiple search results for user details given a username. + * This is false by default, which is sufficient and secure for more deployments. + * Setting this to true may have security consequences. + * + * @param allowMultiple True to allow multiple search results in which case the first result + * returned is used to construct user details, or false to indicate that + * a runtime exception should be raised on multiple search results for user details. + */ + public void setAllowMultipleResults(final boolean allowMultiple) { + this.allowMultipleResults = allowMultiple; + } + + @Override + public UserDetails loadUserByUsername(final String username) { + final SearchResult userResult; + try { + logger.debug("Attempting to get details for user {}.", username); + final Response response = this.userSearchExecutor.search( + this.connectionFactory, + createSearchFilter(this.userSearchExecutor, username)); + logger.debug("LDAP user search response: {}", response); + userResult = response.getResult(); + } catch (final LdapException e) { + throw new RuntimeException("LDAP error fetching details for user.", e); + } + if (userResult.size() == 0) { + throw new UsernameNotFoundException(username + " not found."); + } + if (userResult.size() > 1 && !this.allowMultipleResults) { + throw new IllegalStateException( + "Found multiple results for user which is not allowed (allowMultipleResults=false)."); + } + final LdapEntry userResultEntry = userResult.getEntry(); + final String userDn = userResultEntry.getDn(); + final LdapAttribute userAttribute = userResultEntry.getAttribute(this.userAttributeName); + if (userAttribute == null) { + throw new IllegalStateException(this.userAttributeName + " attribute not found in results."); + } + final String id = userAttribute.getStringValue(); + + final SearchResult roleResult; + try { + logger.debug("Attempting to get roles for user {}.", userDn); + final Response response = this.roleSearchExecutor.search( + this.connectionFactory, + createSearchFilter(this.roleSearchExecutor, userDn)); + logger.debug("LDAP role search response: {}", response); + roleResult = response.getResult(); + } catch (final LdapException e) { + throw new RuntimeException("LDAP error fetching roles for user.", e); + } + LdapAttribute roleAttribute; + final Collection roles = new ArrayList<>(roleResult.size()); + for (final LdapEntry entry : roleResult.getEntries()) { + roleAttribute = entry.getAttribute(this.roleAttributeName); + if (roleAttribute == null) { + logger.warn("Role attribute not found on entry {}", entry); + continue; + } + + for (final String role : roleAttribute.getStringValues()) { + roles.add(new SimpleGrantedAuthority(this.rolePrefix + role.toUpperCase())); + } + + } + + return new User(id, UNKNOWN_PASSWORD, roles); + } + + /** + * Constructs a new search filter using {@link SearchExecutor#searchFilter} as a template and + * the username as a parameter. + * + * @param executor the executor + * @param username the username + * @return Search filter with parameters applied. + */ + private SearchFilter createSearchFilter(final SearchExecutor executor, final String username) { + final SearchFilter filter = new SearchFilter(); + filter.setFilter(executor.getSearchFilter().getFilter()); + filter.setParameter(0, username); + + logger.debug("Constructed LDAP search filter [{}]", filter.format()); + return filter; + } +} diff --git a/cas-server-support-ldap/src/main/java/org/jasig/cas/util/LdapUtils.java b/cas-server-support-ldap/src/main/java/org/jasig/cas/util/LdapUtils.java new file mode 100644 index 000000000000..fe671b0249a9 --- /dev/null +++ b/cas-server-support-ldap/src/main/java/org/jasig/cas/util/LdapUtils.java @@ -0,0 +1,160 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.ldaptive.Connection; +import org.ldaptive.LdapAttribute; +import org.ldaptive.LdapEntry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.Charset; + +/** + * Utilities related to LDAP functions. + * + * @author Scott Battaglia + * @author Misagh Moayyed + * @since 3.0.0 + */ +public final class LdapUtils { + + /** The Constant OBJECTCLASS_ATTRIBUTE. */ + public static final String OBJECTCLASS_ATTRIBUTE = "objectClass"; + + private static final Logger LOGGER = LoggerFactory.getLogger(LdapUtils.class); + + /** + * Instantiates a new ldap utils. + */ + private LdapUtils() { + // private constructor so that no one can instantiate. + } + + /** + * Close the given context and ignore any thrown exception. This is useful + * for typical finally blocks in manual Ldap statements. + * + * @param context the Ldap connection to close + */ + public static void closeConnection(final Connection context) { + if (context != null && context.isOpen()) { + try { + context.close(); + } catch (final Exception ex) { + LOGGER.warn("Could not close ldap connection", ex); + } + } + } + + /** + * Reads a Boolean value from the LdapEntry. + * + * @param ctx the ldap entry + * @param attribute the attribute name + * @return true if the attribute's value matches (case-insensitive) "true", otherwise false + */ + public static Boolean getBoolean(final LdapEntry ctx, final String attribute) { + return getBoolean(ctx, attribute, Boolean.FALSE); + } + + /** + * Reads a Boolean value from the LdapEntry. + * + * @param ctx the ldap entry + * @param attribute the attribute name + * @param nullValue the value which should be returning in case of a null value + * @return true if the attribute's value matches (case-insensitive) "true", otherwise false + */ + public static Boolean getBoolean(final LdapEntry ctx, final String attribute, final Boolean nullValue) { + final String v = getString(ctx, attribute, nullValue.toString()); + if (v != null) { + return v.equalsIgnoreCase(Boolean.TRUE.toString()); + } + return nullValue; + } + + /** + * Reads a Long value from the LdapEntry. + * + * @param ctx the ldap entry + * @param attribute the attribute name + * @return the long value + */ + public static Long getLong(final LdapEntry ctx, final String attribute) { + return getLong(ctx, attribute, Long.MIN_VALUE); + } + + /** + * Reads a Long value from the LdapEntry. + * + * @param entry the ldap entry + * @param attribute the attribute name + * @param nullValue the value which should be returning in case of a null value + * @return the long value + */ + public static Long getLong(final LdapEntry entry, final String attribute, final Long nullValue) { + final String v = getString(entry, attribute, nullValue.toString()); + if (v != null && NumberUtils.isNumber(v)) { + return Long.valueOf(v); + } + return nullValue; + } + + /** + * Reads a String value from the LdapEntry. + * + * @param entry the ldap entry + * @param attribute the attribute name + * @return the string + */ + public static String getString(final LdapEntry entry, final String attribute) { + return getString(entry, attribute, null); + } + + /** + * Reads a String value from the LdapEntry. + * + * @param entry the ldap entry + * @param attribute the attribute name + * @param nullValue the value which should be returning in case of a null value + * @return the string + */ + public static String getString(final LdapEntry entry, final String attribute, final String nullValue) { + final LdapAttribute attr = entry.getAttribute(attribute); + if (attr == null) { + return nullValue; + } + + String v = null; + if (attr.isBinary()) { + final byte[] b = attr.getBinaryValue(); + v = new String(b, Charset.forName("UTF-8")); + } else { + v = attr.getStringValue(); + } + + if (StringUtils.isNotBlank(v)) { + return v; + } + return nullValue; + } +} diff --git a/cas-server-support-ldap/src/main/resources/cas.properties.example b/cas-server-support-ldap/src/main/resources/cas.properties.example new file mode 100644 index 000000000000..439f67ca0e7e --- /dev/null +++ b/cas-server-support-ldap/src/main/resources/cas.properties.example @@ -0,0 +1,80 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +# +# This file only serves a template that outlines the list of properties +# may have to be declared inside the cas.properties file, not only to +# configure ldap authentication, but also enable and configure the lppe feature. +# + +# == General connection settings == + +# Search filter when using a search-and-bind strategy. +ldap.authentication.filter=sAMAccountName=%u + +# Directory URL. Only one URL should be supplied. +# For deployments that need HA directory connections, hardware load balancing with a +# single virtual host is the only reasonable solution. +ldap.authentication.server.urls=ldap://1.1.1.1 + +# Base DN for directory searches. +ldap.authentication.basedn=cn=users,dc=school,dc=edu + +# Manager credentials to bind; only used for searches. +# Only required by directories that prevent anonymous queries (e.g. AD) when performing +# a search-and-bind authentication strategy. +ldap.authentication.manager.userdn=cn=manager,cn=users,dc=school,dc=edu +ldap.authentication.manager.password=password + +# Strongly recommended for AD. +ldap.authentication.ignorePartialResultException=true + +ldap.authentication.jndi.connect.timeout=3000 +ldap.authentication.jndi.read.timeout=3000 +ldap.authentication.jndi.security.level=simple + +# == Connection pooling settings == + +ldap.authentication.pool.minIdle=3 +ldap.authentication.pool.maxIdle=5 +ldap.authentication.pool.maxSize=10 + +# Maximum time in ms to wait for connection to become available +# under pool exhausted condition. +ldap.authentication.pool.maxWait=10000 + +# == Evictor configuration == + +# Period in ms at which evictor process runs. +ldap.authentication.pool.evictionPeriod=600000 + +# Maximum time in ms at which connections can remain idle before +# they become liable to eviction. +ldap.authentication.pool.idleTime=1200000 + +# == Connection testing settings == + +# Set to true to enable connection liveliness testing on evictor +# process runs. Probably results in best performance. +ldap.authentication.pool.testWhileIdle=true + +# Set to true to enable connection liveliness testing before every +# request to borrow an object from the pool. +ldap.authentication.pool.testOnBorrow=false diff --git a/cas-server-support-ldap/src/site/site.xml b/cas-server-support-ldap/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-support-ldap/src/site/site.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/cas-server-3.4.2/cas-server-support-jdbc/src/test/clover/clover.license b/cas-server-support-ldap/src/test/clover/clover.license similarity index 100% rename from cas-server-3.4.2/cas-server-support-jdbc/src/test/clover/clover.license rename to cas-server-support-ldap/src/test/clover/clover.license diff --git a/cas-server-support-ldap/src/test/java/org/jasig/cas/AllTestsSuite.java b/cas-server-support-ldap/src/test/java/org/jasig/cas/AllTestsSuite.java new file mode 100644 index 000000000000..fadaa21e873d --- /dev/null +++ b/cas-server-support-ldap/src/test/java/org/jasig/cas/AllTestsSuite.java @@ -0,0 +1,43 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas; + +import org.jasig.cas.adaptors.ldap.services.LdapServiceRegistryDaoTests; +import org.jasig.cas.authentication.LdapAuthenticationHandlerTests; +import org.jasig.cas.monitor.ConnectionFactoryMonitorTests; +import org.jasig.cas.monitor.PooledConnectionFactoryMonitorTests; +import org.jasig.cas.userdetails.LdapUserDetailsServiceTests; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +/** + * Test suite to run all LDAP tests. + * @author Misagh Moayyed + * @since 4.1.0 + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ + LdapServiceRegistryDaoTests.class, + LdapAuthenticationHandlerTests.class, + ConnectionFactoryMonitorTests.class, + PooledConnectionFactoryMonitorTests.class, + LdapUserDetailsServiceTests.class +}) +public class AllTestsSuite { +} diff --git a/cas-server-support-ldap/src/test/java/org/jasig/cas/adaptors/ldap/AbstractLdapTests.java b/cas-server-support-ldap/src/test/java/org/jasig/cas/adaptors/ldap/AbstractLdapTests.java new file mode 100644 index 000000000000..9d5d4b975943 --- /dev/null +++ b/cas-server-support-ldap/src/test/java/org/jasig/cas/adaptors/ldap/AbstractLdapTests.java @@ -0,0 +1,87 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.ldap; + +import org.apache.commons.io.IOUtils; +import org.jasig.cas.util.ldap.uboundid.InMemoryTestLdapDirectoryServer; +import org.junit.AfterClass; +import org.ldaptive.LdapEntry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.io.ClassPathResource; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; + +/** + * Base class for LDAP tests that provision and deprovision DIRECTORY data as part of test setup/teardown. + * @author Marvin S. Addison + * @author Misagh Moayyed + * @since 4.1.0 + */ +public abstract class AbstractLdapTests implements ApplicationContextAware { + private static InMemoryTestLdapDirectoryServer DIRECTORY; + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + private ApplicationContext context; + + public static void initDirectoryServer(final InputStream ldifFile) throws IOException { + final ClassPathResource properties = new ClassPathResource("ldap.properties"); + final ClassPathResource schema = new ClassPathResource("schema/standard-ldap.schema"); + + DIRECTORY = new InMemoryTestLdapDirectoryServer(properties.getInputStream(), + ldifFile, + schema.getInputStream()); + } + + public static void initDirectoryServer() throws IOException { + initDirectoryServer(new ClassPathResource("ldif/ldap-base.ldif").getInputStream()); + } + + @AfterClass + public static void tearDown() { + IOUtils.closeQuietly(DIRECTORY); + } + + protected static InMemoryTestLdapDirectoryServer getDirectory() { + return DIRECTORY; + } + + protected Collection getEntries() { + return DIRECTORY.getLdapEntries(); + } + + protected String getUsername(final LdapEntry entry) { + final String unameAttr = this.context.getBean("usernameAttribute", String.class); + return entry.getAttribute(unameAttr).getStringValue(); + } + + protected T getBean(final String id, final Class clazz) { + return this.context.getBean(id, clazz); + } + + @Override + public void setApplicationContext(final ApplicationContext applicationContext) { + this.context = applicationContext; + } +} diff --git a/cas-server-support-ldap/src/test/java/org/jasig/cas/adaptors/ldap/services/LdapServiceRegistryDaoTests.java b/cas-server-support-ldap/src/test/java/org/jasig/cas/adaptors/ldap/services/LdapServiceRegistryDaoTests.java new file mode 100644 index 000000000000..bba33dde5e21 --- /dev/null +++ b/cas-server-support-ldap/src/test/java/org/jasig/cas/adaptors/ldap/services/LdapServiceRegistryDaoTests.java @@ -0,0 +1,182 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.ldap.services; + +import org.jasig.cas.adaptors.ldap.AbstractLdapTests; +import org.jasig.cas.services.AbstractRegisteredService; +import org.jasig.cas.services.AnonymousRegisteredServiceUsernameAttributeProvider; +import org.jasig.cas.services.DefaultRegisteredServiceUsernameProvider; +import org.jasig.cas.services.RefuseRegisteredServiceProxyPolicy; +import org.jasig.cas.services.RegexMatchingRegisteredServiceProxyPolicy; +import org.jasig.cas.services.RegexRegisteredService; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.RegisteredServiceImpl; +import org.jasig.cas.services.ReturnAllAttributeReleasePolicy; +import org.jasig.cas.services.ReturnAllowedAttributeReleasePolicy; +import org.jasig.cas.services.ServiceRegistryDao; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import static org.junit.Assert.*; + +/** + * Unit test for {@link LdapServiceRegistryDao} class. + * @author Misagh Moayyed + * @author Marvin S. Addison + * @since 4.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration({"/ldap-context.xml", "/ldap-regservice-test.xml"}) +public class LdapServiceRegistryDaoTests extends AbstractLdapTests { + + @Autowired + private ServiceRegistryDao dao; + + @BeforeClass + public static void bootstrap() throws Exception { + initDirectoryServer(); + } + + @Before + public void setUp() throws Exception { + for (final RegisteredService service : this.dao.load()) { + this.dao.delete(service); + } + } + + @Test + public void verifyEmptyRegistry() { + assertEquals(0, this.dao.load().size()); + } + + @Test + public void verifyNonExistingService() { + assertNull(this.dao.findServiceById(9999991)); + } + + @Test + public void verifySavingServices() { + this.dao.save(getRegisteredService()); + this.dao.save(getRegexRegisteredService()); + final List services = this.dao.load(); + assertEquals(2, services.size()); + } + + @Test + public void verifyUpdatingServices() { + this.dao.save(getRegisteredService()); + final List services = this.dao.load(); + + final AbstractRegisteredService rs = (AbstractRegisteredService) this.dao.findServiceById(services.get(0).getId()); + assertNotNull(rs); + rs.setEvaluationOrder(9999); + rs.setUsernameAttributeProvider(new DefaultRegisteredServiceUsernameProvider()); + rs.setName("Another Test Service"); + rs.setDescription("The new description"); + rs.setServiceId("https://hello.world"); + rs.setProxyPolicy(new RegexMatchingRegisteredServiceProxyPolicy("https")); + rs.setAttributeReleasePolicy(new ReturnAllowedAttributeReleasePolicy()); + assertNotNull(this.dao.save(rs)); + + final RegisteredService rs3 = this.dao.findServiceById(rs.getId()); + assertEquals(rs3.getName(), rs.getName()); + assertEquals(rs3.getDescription(), rs.getDescription()); + assertEquals(rs3.getEvaluationOrder(), rs.getEvaluationOrder()); + assertEquals(rs3.getUsernameAttributeProvider(), rs.getUsernameAttributeProvider()); + assertEquals(rs3.getProxyPolicy(), rs.getProxyPolicy()); + assertEquals(rs3.getUsernameAttributeProvider(), rs.getUsernameAttributeProvider()); + assertEquals(rs3.getServiceId(), rs.getServiceId()); + } + + @Test + public void verifySavingServiceChangesDn() { + this.dao.save(getRegisteredService()); + final List services = this.dao.load(); + + final AbstractRegisteredService rs = (AbstractRegisteredService) this.dao.findServiceById(services.get(0).getId()); + final long originalId = rs.getId(); + assertNotNull(rs); + rs.setId(666); + assertNotNull(this.dao.save(rs)); + assertNotEquals(rs.getId(), originalId); + } + + @Test + public void verifyDeletingSingleService() throws Exception { + final RegisteredService rs = getRegexRegisteredService(); + final RegisteredService rs2 = getRegisteredService(); + this.dao.save(rs2); + this.dao.save(rs); + List services = this.dao.load(); + this.dao.delete(rs2); + + services = this.dao.load(); + assertEquals(1, services.size()); + assertEquals(services.get(0).getId(), rs.getId()); + assertEquals(services.get(0).getName(), rs.getName()); + } + + @Test + public void verifyDeletingServices() throws Exception { + this.dao.save(getRegisteredService()); + this.dao.save(getRegexRegisteredService()); + final List services = this.dao.load(); + for (final RegisteredService registeredService : services) { + this.dao.delete(registeredService); + } + assertEquals(0, this.dao.load().size()); + } + + private RegisteredService getRegisteredService() { + final AbstractRegisteredService rs = new RegisteredServiceImpl(); + rs.setName("Service Name1"); + rs.setProxyPolicy(new RefuseRegisteredServiceProxyPolicy()); + rs.setUsernameAttributeProvider(new AnonymousRegisteredServiceUsernameAttributeProvider()); + rs.setDescription("Service description"); + rs.setServiceId("https://?.edu/**"); + rs.setTheme("the theme name"); + rs.setEvaluationOrder(123); + rs.setAttributeReleasePolicy(new ReturnAllAttributeReleasePolicy()); + rs.setRequiredHandlers(new HashSet(Arrays.asList("handler8", "handle92"))); + return rs; + } + + private RegisteredService getRegexRegisteredService() { + final AbstractRegisteredService rs = new RegexRegisteredService(); + rs.setName("Service Name Regex"); + rs.setProxyPolicy(new RefuseRegisteredServiceProxyPolicy()); + rs.setUsernameAttributeProvider(new AnonymousRegisteredServiceUsernameAttributeProvider()); + rs.setDescription("Service description"); + rs.setServiceId("^http?://.+"); + rs.setTheme("the theme name"); + rs.setEvaluationOrder(123); + rs.setDescription("Here is another description"); + rs.setRequiredHandlers(new HashSet(Arrays.asList("handler1", "handler2"))); + return rs; + } +} diff --git a/cas-server-support-ldap/src/test/java/org/jasig/cas/authentication/LdapAuthenticationHandlerTests.java b/cas-server-support-ldap/src/test/java/org/jasig/cas/authentication/LdapAuthenticationHandlerTests.java new file mode 100644 index 000000000000..a1e7768429b9 --- /dev/null +++ b/cas-server-support-ldap/src/test/java/org/jasig/cas/authentication/LdapAuthenticationHandlerTests.java @@ -0,0 +1,86 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.adaptors.ldap.AbstractLdapTests; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ldaptive.LdapEntry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import javax.security.auth.login.FailedLoginException; + +import static org.junit.Assert.*; + +/** + * Unit test for {@link LdapAuthenticationHandler}. + * + * @author Marvin S. Addison + * @author Misagh Moayyed + * @since 4.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration({"/ldap-context.xml", "/authn-context.xml"}) +public class LdapAuthenticationHandlerTests extends AbstractLdapTests { + + @Autowired + private AuthenticationHandler handler; + + @BeforeClass + public static void bootstrap() throws Exception { + initDirectoryServer(); + } + + @Test + public void verifyAuthenticateSuccess() throws Exception { + for (final LdapEntry entry : this.getEntries()) { + final String username = getUsername(entry); + final String psw = entry.getAttribute("userPassword").getStringValue(); + final HandlerResult result = this.handler.authenticate( + new UsernamePasswordCredential(username, psw)); + assertNotNull(result.getPrincipal()); + assertEquals(username, result.getPrincipal().getId()); + assertEquals( + entry.getAttribute("displayName").getStringValue(), + result.getPrincipal().getAttributes().get("displayName")); + assertEquals( + entry.getAttribute("mail").getStringValue(), + result.getPrincipal().getAttributes().get("mail")); + } + } + + @Test(expected=FailedLoginException.class) + public void verifyAuthenticateFailure() throws Exception { + for (final LdapEntry entry : this.getEntries()) { + final String username = getUsername(entry); + this.handler.authenticate(new UsernamePasswordCredential(username, "badpassword")); + fail("Should have thrown FailedLoginException."); + + } + } + + @Test(expected=FailedLoginException.class) + public void verifyAuthenticateNotFound() throws Exception { + this.handler.authenticate(new UsernamePasswordCredential("notfound", "somepwd")); + fail("Should have thrown FailedLoginException."); + } +} diff --git a/cas-server-support-ldap/src/test/java/org/jasig/cas/monitor/ConnectionFactoryMonitorTests.java b/cas-server-support-ldap/src/test/java/org/jasig/cas/monitor/ConnectionFactoryMonitorTests.java new file mode 100644 index 000000000000..5f639f60b794 --- /dev/null +++ b/cas-server-support-ldap/src/test/java/org/jasig/cas/monitor/ConnectionFactoryMonitorTests.java @@ -0,0 +1,52 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import org.jasig.cas.adaptors.ldap.AbstractLdapTests; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.*; + +/** + * Unit test for {@link ConnectionFactoryMonitor} class. + * @since 3.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration({"/ldap-context.xml", "/ldap-monitor-test.xml"}) +public class ConnectionFactoryMonitorTests extends AbstractLdapTests { + + + @Autowired + private ConnectionFactoryMonitor monitor; + + @BeforeClass + public static void bootstrap() throws Exception { + initDirectoryServer(); + } + + @Test + public void verifyObserve() throws Exception { + assertEquals(StatusCode.OK, monitor.observe().getCode()); + } +} diff --git a/cas-server-support-ldap/src/test/java/org/jasig/cas/monitor/PooledConnectionFactoryMonitorTests.java b/cas-server-support-ldap/src/test/java/org/jasig/cas/monitor/PooledConnectionFactoryMonitorTests.java new file mode 100644 index 000000000000..def0c3bffedd --- /dev/null +++ b/cas-server-support-ldap/src/test/java/org/jasig/cas/monitor/PooledConnectionFactoryMonitorTests.java @@ -0,0 +1,52 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.monitor; + +import org.jasig.cas.adaptors.ldap.AbstractLdapTests; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.*; + +/** + * Unit test for {@link PooledConnectionFactoryMonitor} class. + * @author Marvin S. Addison + * @since 4.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration({"/ldap-context.xml", "/ldap-poolmonitor-test.xml"}) +public class PooledConnectionFactoryMonitorTests extends AbstractLdapTests { + + @Autowired + private PooledConnectionFactoryMonitor monitor; + + @BeforeClass + public static void bootstrap() throws Exception { + initDirectoryServer(); + } + + @Test + public void verifyObserve() throws Exception { + assertEquals(StatusCode.OK, monitor.observe().getCode()); + } +} diff --git a/cas-server-support-ldap/src/test/java/org/jasig/cas/userdetails/LdapUserDetailsServiceTests.java b/cas-server-support-ldap/src/test/java/org/jasig/cas/userdetails/LdapUserDetailsServiceTests.java new file mode 100644 index 000000000000..f7adf0d7be88 --- /dev/null +++ b/cas-server-support-ldap/src/test/java/org/jasig/cas/userdetails/LdapUserDetailsServiceTests.java @@ -0,0 +1,78 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.userdetails; + +import org.jasig.cas.adaptors.ldap.AbstractLdapTests; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ldaptive.LdapEntry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.*; + +/** + * Unit test for the {@link LdapUserDetailsService} class. + *

+ * The virginiaTechGroup schema MUST be installed on the target directories prior to running this test. + * + * @author Marvin Addison + * @since 4.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration({"/ldap-context.xml", "/ldap-userdetails-test.xml"}) +public class LdapUserDetailsServiceTests extends AbstractLdapTests { + + private static final String CAS_SERVICE_DETAILS_OBJ_CLASS = "casServiceUserDetails"; + + @Autowired + private LdapUserDetailsService userDetailsService; + + @BeforeClass + public static void bootstrap() throws Exception { + initDirectoryServer(); + } + + @Test + public void verifyLoadUserByUsername() throws Exception { + for (final LdapEntry entry : getEntries()) { + + if (entry.getAttribute("objectclass").getStringValues().contains(CAS_SERVICE_DETAILS_OBJ_CLASS)) { + final String username = getUsername(entry); + final UserDetails user = userDetailsService.loadUserByUsername(username); + assertEquals(username, user.getUsername()); + assertTrue(hasAuthority(user, "ROLE_ADMINISTRATORS")); + assertTrue(hasAuthority(user, "ROLE_USERS")); + } + } + } + + private boolean hasAuthority(final UserDetails user, final String name) { + for (final GrantedAuthority authority : user.getAuthorities()) { + if (authority.getAuthority().equals(name)) { + return true; + } + } + return false; + } +} diff --git a/cas-server-support-ldap/src/test/java/org/jasig/cas/util/LdapTestUtils.java b/cas-server-support-ldap/src/test/java/org/jasig/cas/util/LdapTestUtils.java new file mode 100644 index 000000000000..e08859dccbe4 --- /dev/null +++ b/cas-server-support-ldap/src/test/java/org/jasig/cas/util/LdapTestUtils.java @@ -0,0 +1,102 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import com.unboundid.ldap.sdk.AddRequest; +import com.unboundid.ldap.sdk.Attribute; +import com.unboundid.ldap.sdk.LDAPConnection; +import org.ldaptive.LdapAttribute; +import org.ldaptive.LdapEntry; +import org.ldaptive.io.LdifReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collection; + +/** + * Utility class used by all tests that provision and deprovision LDAP test data. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public final class LdapTestUtils { + + /** Placeholder for base DN in LDIF files. */ + private static final String BASE_DN_PLACEHOLDER = "${ldapBaseDn}"; + + /** System-wide newline character string. */ + private static final String NEWLINE = System.getProperty("line.separator"); + + private static final Logger LOGGER = LoggerFactory.getLogger(LdapTestUtils.class); + + /** Private constructor of utility class. */ + private LdapTestUtils() { + } + + /** + * Reads an LDIF into a collection of LDAP entries. The components performs a simple property + * replacement in the LDIF data where

${ldapBaseDn}
is replaced with the environment-specific base + * DN. + * + * @param ldif LDIF resource, typically a file on filesystem or classpath. + * @param baseDn The directory branch where the entry resides. + * + * @return LDAP entries contained in the LDIF. + * + * @throws IOException On IO errors reading LDIF. + */ + public static Collection readLdif(final InputStream ldif, final String baseDn) throws IOException { + final StringBuilder builder = new StringBuilder(); + try (final BufferedReader reader = new BufferedReader(new InputStreamReader(ldif))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.contains(BASE_DN_PLACEHOLDER)) { + builder.append(line.replace(BASE_DN_PLACEHOLDER, baseDn)); + } else { + builder.append(line); + } + builder.append(NEWLINE); + } + } + return new LdifReader(new StringReader(builder.toString())).read().getEntries(); + } + + /** + * Creates the given LDAP entries. + * + * @param connection Open LDAP connection used to connect to directory. + * @param entries Collection of LDAP entries. + * + * @throws Exception On LDAP errors. + */ + public static void createLdapEntries(final LDAPConnection connection, final Collection entries) throws Exception { + for (final LdapEntry entry : entries) { + final Collection attrs = new ArrayList<>(entry.getAttributeNames().length); + for (final LdapAttribute a : entry.getAttributes()) { + attrs.add(new Attribute(a.getName(), a.getStringValues())); + } + connection.add(new AddRequest(entry.getDn(), attrs)); + } + } +} diff --git a/cas-server-support-ldap/src/test/java/org/jasig/cas/util/ldap/uboundid/InMemoryTestLdapDirectoryServer.java b/cas-server-support-ldap/src/test/java/org/jasig/cas/util/ldap/uboundid/InMemoryTestLdapDirectoryServer.java new file mode 100644 index 000000000000..a49a33d600bd --- /dev/null +++ b/cas-server-support-ldap/src/test/java/org/jasig/cas/util/ldap/uboundid/InMemoryTestLdapDirectoryServer.java @@ -0,0 +1,180 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util.ldap.uboundid; + +import com.unboundid.ldap.listener.InMemoryDirectoryServer; +import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; +import com.unboundid.ldap.listener.InMemoryListenerConfig; +import com.unboundid.ldap.sdk.LDAPConnection; +import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.schema.Schema; +import com.unboundid.util.ssl.KeyStoreKeyManager; +import com.unboundid.util.ssl.SSLUtil; +import com.unboundid.util.ssl.TrustStoreTrustManager; +import org.apache.commons.io.IOUtils; +import org.jasig.cas.util.LdapTestUtils; +import org.ldaptive.LdapEntry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; + +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collection; +import java.util.Properties; + +/** + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class InMemoryTestLdapDirectoryServer implements Closeable { + private static final Logger LOGGER = LoggerFactory.getLogger(InMemoryTestLdapDirectoryServer.class); + + private final InMemoryDirectoryServer directoryServer; + + private Collection ldapEntries; + + /** + * Instantiates a new Ldap directory server. + * Parameters need to be streams so they can be read from JARs. + */ + public InMemoryTestLdapDirectoryServer(final InputStream properties, + final InputStream ldifFile, + final InputStream schemaFile) { + try { + final Properties p = new Properties(); + p.load(properties); + + final InMemoryDirectoryServerConfig config = + new InMemoryDirectoryServerConfig(p.getProperty("ldap.rootDn")); + config.addAdditionalBindCredentials(p.getProperty("ldap.managerDn"), p.getProperty("ldap.managerPassword")); + + final File keystoreFile = File.createTempFile("key", "store"); + try (final OutputStream outputStream = new FileOutputStream(keystoreFile)) { + IOUtils.copy(new ClassPathResource("/ldapServerTrustStore").getInputStream(), outputStream); + } + + final String serverKeyStorePath = keystoreFile.getCanonicalPath(); + final SSLUtil serverSSLUtil = new SSLUtil( + new KeyStoreKeyManager(serverKeyStorePath, "changeit".toCharArray()), new TrustStoreTrustManager(serverKeyStorePath)); + final SSLUtil clientSSLUtil = new SSLUtil(new TrustStoreTrustManager(serverKeyStorePath)); + config.setListenerConfigs( + InMemoryListenerConfig.createLDAPConfig("LDAP", // Listener name + null, // Listen address. (null = listen on all interfaces) + 1389, // Listen port (0 = automatically choose an available port) + serverSSLUtil.createSSLSocketFactory()), // StartTLS factory + InMemoryListenerConfig.createLDAPSConfig("LDAPS", // Listener name + null, // Listen address. (null = listen on all interfaces) + 1636, // Listen port (0 = automatically choose an available port) + serverSSLUtil.createSSLServerSocketFactory(), // Server factory + clientSSLUtil.createSSLSocketFactory())); // Client factory + + config.setEnforceSingleStructuralObjectClass(false); + config.setEnforceAttributeSyntaxCompliance(true); + + + final File file = File.createTempFile("ldap", "schema"); + try (final OutputStream outputStream = new FileOutputStream(file)) { + IOUtils.copy(schemaFile, outputStream); + } + + final Schema s = Schema.mergeSchemas(Schema.getSchema(file)); + config.setSchema(s); + + + this.directoryServer = new InMemoryDirectoryServer(config); + LOGGER.debug("Populating directory..."); + + final File ldif = File.createTempFile("ldiff", "file"); + try (final OutputStream outputStream = new FileOutputStream(ldif)) { + IOUtils.copy(ldifFile, outputStream); + } + + this.directoryServer.importFromLDIF(true, ldif.getCanonicalPath()); + this.directoryServer.restartServer(); + + final LDAPConnection c = getConnection(); + LOGGER.debug("Connected to {}:{}", c.getConnectedAddress(), c.getConnectedPort()); + + populateDefaultEntries(c); + + c.close(); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Instantiates a new Ldap directory server. + */ + public InMemoryTestLdapDirectoryServer(final File properties, final File ldifFile, final File... schemaFile) + throws FileNotFoundException { + this(new FileInputStream(properties), + new FileInputStream(ldifFile), + new FileInputStream(ldifFile)); + } + + private void populateDefaultEntries(final LDAPConnection c) throws Exception { + populateEntries(c, new ClassPathResource("ldif/users-groups.ldif").getInputStream()); + } + + public void populateEntries(final InputStream rs) throws Exception { + populateEntries(getConnection(), rs); + } + + protected void populateEntries(final LDAPConnection c, final InputStream rs) throws Exception { + this.ldapEntries = LdapTestUtils.readLdif(rs, getBaseDn()); + LdapTestUtils.createLdapEntries(c, ldapEntries); + populateEntriesInternal(c); + } + + protected void populateEntriesInternal(final LDAPConnection c) {} + + public String getBaseDn() { + return this.directoryServer.getBaseDNs().get(0).toNormalizedString(); + } + + public Collection getLdapEntries() { + return this.ldapEntries; + } + + public LDAPConnection getConnection() throws LDAPException { + return this.directoryServer.getConnection(); + } + + @Override + public void close() throws IOException { + LOGGER.debug("Shutting down LDAP server..."); + this.directoryServer.shutDown(true); + LOGGER.debug("Shut down LDAP server."); + } + + @Override + protected void finalize() throws Throwable { + LOGGER.debug("Finalizing..."); + close(); + super.finalize(); + } +} diff --git a/cas-server-support-ldap/src/test/resources/authn-context.xml b/cas-server-support-ldap/src/test/resources/authn-context.xml new file mode 100644 index 000000000000..f203f99e825a --- /dev/null +++ b/cas-server-support-ldap/src/test/resources/authn-context.xml @@ -0,0 +1,55 @@ + + + + + + + + + + displayName + mail + givenName + + + + + + + + + + diff --git a/cas-server-support-ldap/src/test/resources/ldap-context.xml b/cas-server-support-ldap/src/test/resources/ldap-context.xml new file mode 100644 index 000000000000..e578ad00abce --- /dev/null +++ b/cas-server-support-ldap/src/test/resources/ldap-context.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-support-ldap/src/test/resources/ldap-monitor-test.xml b/cas-server-support-ldap/src/test/resources/ldap-monitor-test.xml new file mode 100644 index 000000000000..94037b015867 --- /dev/null +++ b/cas-server-support-ldap/src/test/resources/ldap-monitor-test.xml @@ -0,0 +1,37 @@ + + + + + + + + diff --git a/cas-server-support-ldap/src/test/resources/ldap-poolmonitor-test.xml b/cas-server-support-ldap/src/test/resources/ldap-poolmonitor-test.xml new file mode 100644 index 000000000000..cf322d71f184 --- /dev/null +++ b/cas-server-support-ldap/src/test/resources/ldap-poolmonitor-test.xml @@ -0,0 +1,42 @@ + + + + + + + + diff --git a/cas-server-support-ldap/src/test/resources/ldap-regservice-test.xml b/cas-server-support-ldap/src/test/resources/ldap-regservice-test.xml new file mode 100644 index 000000000000..a006eb5456e6 --- /dev/null +++ b/cas-server-support-ldap/src/test/resources/ldap-regservice-test.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + diff --git a/cas-server-support-ldap/src/test/resources/ldap-userdetails-test.xml b/cas-server-support-ldap/src/test/resources/ldap-userdetails-test.xml new file mode 100644 index 000000000000..b71df8061826 --- /dev/null +++ b/cas-server-support-ldap/src/test/resources/ldap-userdetails-test.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + mail + + + + uugid + + + diff --git a/cas-server-support-ldap/src/test/resources/ldap.properties b/cas-server-support-ldap/src/test/resources/ldap.properties new file mode 100644 index 000000000000..b6ddb6e4b92b --- /dev/null +++ b/cas-server-support-ldap/src/test/resources/ldap.properties @@ -0,0 +1,65 @@ +#======================================== +# General properties +#======================================== +ldap.url=ldap://localhost:1389 + +# Start TLS for SSL connections +ldap.useStartTLS=false + +# Directory root DN +ldap.rootDn=dc=example,dc=org + +# Base DN of users to be authenticated +ldap.baseDn=ou=people,dc=example,dc=org + +# LDAP connection timeout in milliseconds +ldap.connectTimeout=3000 + +# Manager credential DN +ldap.managerDn=cn=Directory Manager,dc=example,dc=org + +# Manager credential password +ldap.managerPassword=Password + +#======================================== +# LDAP connection pool configuration +#======================================== +ldap.pool.minSize=1 +ldap.pool.maxSize=10 +ldap.pool.validateOnCheckout=false +ldap.pool.validatePeriodically=true + +# Amount of time in milliseconds to block on pool exhausted condition +# before giving up. +ldap.pool.blockWaitTime=3000 + +# Frequency of connection validation in seconds +# Only applies if validatePeriodically=true +ldap.pool.validatePeriod=300 + +# Attempt to prune connections every N seconds +ldap.pool.prunePeriod=300 + +# Maximum amount of time an idle connection is allowed to be in +# pool before it is liable to be removed/destroyed +ldap.pool.idleTime=600 + +#======================================== +# Authentication +#======================================== +ldap.authn.searchFilter=(mail={user}) + +# Ldap domain used to resolve dn +ldap.domain=example.org + +#======================================== +# User Details +#======================================== +ldap.role.baseDn=ou=people,dc=example,dc=org +ldap.role.searchFilter=&(objectclass=casServiceUserDetails)(member={0}) +ldap.user.searchFilter=&(objectclass=casServiceUserDetails)(mail={0}) + +#======================================== +# Registered Service +#======================================== +ldap.service.baseDn=ou=people,dc=example,dc=org diff --git a/cas-server-support-ldap/src/test/resources/ldapServerTrustStore b/cas-server-support-ldap/src/test/resources/ldapServerTrustStore new file mode 100644 index 000000000000..beb22de12bf8 Binary files /dev/null and b/cas-server-support-ldap/src/test/resources/ldapServerTrustStore differ diff --git a/cas-server-support-ldap/src/test/resources/ldif/ldap-base.ldif b/cas-server-support-ldap/src/test/resources/ldif/ldap-base.ldif new file mode 100644 index 000000000000..e8c0083f0afd --- /dev/null +++ b/cas-server-support-ldap/src/test/resources/ldif/ldap-base.ldif @@ -0,0 +1,87 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +dn: dc=example,dc=org +objectclass: domain +objectclass: top +dc: example + +dn: ou=groups,dc=example,dc=org +objectclass: top +objectclass: organizationalUnit +ou: groups + +dn: ou=people,dc=example,dc=org +objectclass: top +objectclass: organizationalUnit +ou: people + +dn: uid=rod,ou=people,dc=example,dc=org +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +cn: Rod Johnson +sn: Johnson +uid: rod +userPassword: koala + +dn: uid=dianne,ou=people,dc=example,dc=org +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +cn: Dianne Emu +sn: Emu +uid: dianne +userPassword: emu + +dn: uid=scott,ou=people,dc=example,dc=org +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +cn: Scott +sn: Wombat +uid: scott +userPassword: wombat + +dn: cn=user,ou=groups,dc=example,dc=org +objectclass: top +objectclass: groupOfNames +cn: user +member: uid=rod,ou=people,dc=example,dc=org +member: uid=dianne,ou=people,dc=example,dc=org +member: uid=scott,ou=people,dc=example,dc=org + +dn: cn=teller,ou=groups,dc=example,dc=org +objectclass: top +objectclass: groupOfNames +cn: teller +member: uid=rod,ou=people,dc=example,dc=org +member: dianne=rod,ou=people,dc=example,dc=org + +dn: cn=supervisor,ou=groups,dc=example,dc=org +objectclass: top +objectclass: groupOfNames +cn: supervisor +member: uid=rod,ou=people,dc=example,dc=org + + + diff --git a/cas-server-support-ldap/src/test/resources/ldif/users-groups.ldif b/cas-server-support-ldap/src/test/resources/ldif/users-groups.ldif new file mode 100644 index 000000000000..d014b04f8462 --- /dev/null +++ b/cas-server-support-ldap/src/test/resources/ldif/users-groups.ldif @@ -0,0 +1,81 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +dn: CN=mmoayyed,ou=people,dc=example,dc=org +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: ad +objectClass: inetOrgPerson +cn: Misagh Moayyed +sn: Moayyed +givenName: Misagh +displayName: Misagh Moayyed +sAMAccountName: mmoayyed +mail: mmoayyed@example.org +userPrincipalName: mmoayyed@example.org +userAccountControl: 66048 +distinguishedName: CN=mmoayyed,ou=people,dc=example,dc=org +userPassword: misagh +unicodePwd: ignoredForTests + + +dn: CN=casTest,ou=people,dc=example,dc=org +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +objectClass: ad +objectClass: account +cn: CAS Test +givenName: CAS +sn: CAS +uid: CASTest +displayName: CAS Test +sAMAccountName: casTest +distinguishedName: CN=casTest,ou=people,dc=example,dc=org +mail: casTest@example.org +userPassword: thePassw0rd +unicodePwd: ignoredForTests +userPrincipalName: castest@example.org +userAccountControl: 66048 +host: localhost + +dn: cn=Administrators,ou=people,dc=example,dc=org +cn: Administrators +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +objectClass: casServiceUserDetails +objectClass: ad +unicodePwd: ignoredForTests +uid: 31415926535 +sAMAccountName: administrators +sn: Administrators +contactPerson: uid=9000000100,ou=people,dc=example,dc=org +creationDate: 2012 12 21 +uugid: Administrators +uugid: Users +member: cn=Administrators,ou=people,dc=example,dc=org +mail: Administrators@example.org +userAccountControl: 66048 +distinguishedName: cn=Administrators,ou=people,dc=example,dc=org +userPassword: thePassw0rd +displayName: Administrators diff --git a/cas-server-support-ldap/src/test/resources/log4j2.xml b/cas-server-support-ldap/src/test/resources/log4j2.xml new file mode 100644 index 000000000000..23fab80c20fc --- /dev/null +++ b/cas-server-support-ldap/src/test/resources/log4j2.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-support-ldap/src/test/resources/schema/standard-ldap.schema b/cas-server-support-ldap/src/test/resources/schema/standard-ldap.schema new file mode 100644 index 000000000000..37f3183f027e --- /dev/null +++ b/cas-server-support-ldap/src/test/resources/schema/standard-ldap.schema @@ -0,0 +1,1656 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# This file contains a set of standard schema definitions from various RFCs and +# Internet Drafts. It is not intended to be a complete comprehensive schema +# for all purposes, but it may be used by the LDAP SDK for cases in which +# schema information may be required and no other definitions are available. +# +# Definitions in this class come from the following sources: +# * RFC 2798: +# Definition of the inetOrgPerson LDAP Object Class +# * RFC 3045: +# Storing Vendor Information in the LDAP Root DSE +# * RFC 3112: +# LDAP Authentication Password Schema +# * RFC 3296: +# Named Subordinate References in LDAP Directories +# * RFC 4512: +# LDAP Directory Information Models +# * RFC 4519: +# LDAP Schema for User Applications +# * RFC 4523: +# LDAP Schema Definitions for X.509 Certificates +# * RFC 4524: +# COSINE LDAP/X.500 Schema +# * RFC 4530: +# LDAP entryUUID Operational Attribute +# * RFC 5020: +# The LDAP entryDN Operational Attribute +# * draft-good-ldap-changelog: +# Definition of an Object Class to Hold LDAP Change Records +# * draft-howard-namedobject: +# A Structural Object Class for Arbitrary Auxiliary Object Classes +# * draft-ietf-boreham-numsubordinates: +# numSubordinates LDAP Operational Attribute +# * draft-ietf-ldup-subentry: +# LDAP Subentry Schema +dn: cn=schema +objectClass: top +objectClass: ldapSubEntry +objectClass: subschema +cn: schema +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.3 + DESC 'Attribute Type Description' + X-ORIGIN 'RFC 4517' ) +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.6 + DESC 'Bit String' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.7 + DESC 'Boolean' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.11 + DESC 'Country String' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.14 + DESC 'Delivery Method' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.15 + DESC 'Directory String' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.16 + DESC 'DIT Content Rule Description' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.17 + DESC 'DIT Structure Rule Description' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.12 + DESC 'DN' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.21 + DESC 'Enhanced Guide' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.22 + DESC 'Facsimile Telephone Number' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.23 + DESC 'Fax' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.24 + DESC 'Generalized Time' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.25 + DESC 'Guide' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.26 + DESC 'IA5 String' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.27 + DESC 'INTEGER' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.28 + DESC 'JPEG' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.54 + DESC 'LDAP Syntax Description' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.30 + DESC 'Matching Rule Description' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.31 + DESC 'Matching Rule Use Description' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.34 + DESC 'Name And Optional UID' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.35 + DESC 'Name Form Description' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.36 + DESC 'Numeric String' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.37 + DESC 'Object Class Description' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.40 + DESC 'Octet String' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.38 + DESC 'OID' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.39 + DESC 'Other Mailbox' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.41 + DESC 'Postal Address' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.44 + DESC 'Printable String' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.58 + DESC 'Substring Assertion' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.50 + DESC 'Telephone Number' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.51 + DESC 'Teletex Terminal Identifier' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.52 + DESC 'Telex Number' + X-ORIGIN 'RFC 4517') +ldapSyntaxes: ( 1.3.6.1.4.1.1466.115.121.1.53 + DESC 'UTC Time' + X-ORIGIN 'RFC 4517') +matchingRules: ( 2.5.13.16 + NAME 'bitStringMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.6 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.13 + NAME 'booleanMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 1.3.6.1.4.1.1466.109.114.1 + NAME 'caseExactIA5Match' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.5 + NAME 'caseExactMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.6 + NAME 'caseExactOrderingMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.7 + NAME 'caseExactSubstringsMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 1.3.6.1.4.1.1466.109.114.2 + NAME 'caseIgnoreIA5Match' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 1.3.6.1.4.1.1466.109.114.3 + NAME 'caseIgnoreIA5SubstringsMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.11 + NAME 'caseIgnoreListMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.12 + NAME 'caseIgnoreListSubstringsMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.2 + NAME 'caseIgnoreMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.3 + NAME 'caseIgnoreOrderingMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.4 + NAME 'caseIgnoreSubstringsMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.31 + NAME 'directoryStringFirstComponentMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.1 + NAME 'distinguishedNameMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.27 + NAME 'generalizedTimeMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.28 + NAME 'generalizedTimeOrderingMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.29 + NAME 'integerFirstComponentMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.14 + NAME 'integerMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.15 + NAME 'integerOrderingMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.33 + NAME 'keywordMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.8 + NAME 'numericStringMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.9 + NAME 'numericStringOrderingMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.10 + NAME 'numericStringSubstringsMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.30 + NAME 'objectIdentifierFirstComponentMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.0 + NAME 'objectIdentifierMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.17 + NAME 'octetStringMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.18 + NAME 'octetStringOrderingMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.20 + NAME 'telephoneNumberMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.21 + NAME 'telephoneNumberSubstringsMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.23 + NAME 'uniqueMemberMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.34 + X-ORIGIN 'RFC 4517' ) +matchingRules: ( 2.5.13.32 + NAME 'wordMatch' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4517' ) +attributeTypes: ( 2.5.4.0 + NAME 'objectClass' + EQUALITY objectIdentifierMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 2.5.4.1 + NAME 'aliasedObjectName' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + SINGLE-VALUE + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 2.5.18.3 + NAME 'creatorsName' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + SINGLE-VALUE + NO-USER-MODIFICATION + USAGE directoryOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 2.55.183.1 + NAME 'contactPerson' + SINGLE-VALUE + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 22.5.183.1 + NAME 'member' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.5.1466.115.121.1.12 + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 12.5.183.12 + NAME 'uugid' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.62 + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 12.5.183.1 + NAME 'creationDate' + SINGLE-VALUE + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 2.5.18.1 + NAME 'createTimestamp' + EQUALITY generalizedTimeMatch + ORDERING generalizedTimeOrderingMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + SINGLE-VALUE + NO-USER-MODIFICATION + USAGE directoryOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 2.5.18.4 + NAME 'modifiersName' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + SINGLE-VALUE + NO-USER-MODIFICATION + USAGE directoryOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 2.5.18.2 + NAME 'modifyTimestamp' + EQUALITY generalizedTimeMatch + ORDERING generalizedTimeOrderingMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + SINGLE-VALUE + NO-USER-MODIFICATION + USAGE directoryOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 2.5.21.9 + NAME 'structuralObjectClass' + EQUALITY objectIdentifierMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 + SINGLE-VALUE + NO-USER-MODIFICATION + USAGE directoryOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 2.5.21.10 + NAME 'governingStructureRule' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE + NO-USER-MODIFICATION + USAGE directoryOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 2.5.18.10 + NAME 'subschemaSubentry' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + SINGLE-VALUE + NO-USER-MODIFICATION + USAGE directoryOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 2.5.21.6 + NAME 'objectClasses' + EQUALITY objectIdentifierFirstComponentMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.37 + USAGE directoryOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 2.5.21.5 + NAME 'attributeTypes' + EQUALITY objectIdentifierFirstComponentMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.3 + USAGE directoryOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 2.5.21.4 + NAME 'matchingRules' + EQUALITY objectIdentifierFirstComponentMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.30 + USAGE directoryOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 2.5.21.8 + NAME 'matchingRuleUse' + EQUALITY objectIdentifierFirstComponentMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.31 + USAGE directoryOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 1.3.6.1.4.1.1466.101.120.16 + NAME 'ldapSyntaxes' + EQUALITY objectIdentifierFirstComponentMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.54 + USAGE directoryOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 2.5.21.2 + NAME 'dITContentRules' + EQUALITY objectIdentifierFirstComponentMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.16 + USAGE directoryOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 2.5.21.1 + NAME 'dITStructureRules' + EQUALITY integerFirstComponentMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.17 + USAGE directoryOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 2.5.21.7 + NAME 'nameForms' + EQUALITY objectIdentifierFirstComponentMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.35 + USAGE directoryOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 1.3.6.1.4.1.1466.101.120.6 + NAME 'altServer' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + USAGE dSAOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 1.3.6.1.4.1.1466.101.120.5 + NAME 'namingContexts' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + USAGE dSAOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 1.3.6.1.4.1.1466.101.120.13 + NAME 'supportedControl' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 + USAGE dSAOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 1.3.6.1.4.1.1466.101.120.7 + NAME 'supportedExtension' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 + USAGE dSAOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 1.3.6.1.4.1.4203.1.3.5 + NAME 'supportedFeatures' + EQUALITY objectIdentifierMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 + USAGE dSAOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 1.3.6.1.4.1.1466.101.120.15 + NAME 'supportedLDAPVersion' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + USAGE dSAOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 1.3.6.1.4.1.1466.101.120.14 + NAME 'supportedSASLMechanisms' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + USAGE dSAOperation + X-ORIGIN 'RFC 4512' ) +attributeTypes: ( 2.5.4.41 + NAME 'name' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.15 + NAME 'businessCategory' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.6 + NAME 'c' + SUP name + SYNTAX 1.3.6.1.4.1.1466.115.121.1.11 + SINGLE-VALUE + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.3 + NAME 'cn' + SUP name + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 0.9.2342.19200300.100.1.25 + NAME 'dc' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.13 + NAME 'description' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.27 + NAME 'destinationIndicator' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.49 + NAME 'distinguishedName' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.46 + NAME 'dnQualifier' + EQUALITY caseIgnoreMatch + ORDERING caseIgnoreOrderingMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.47 + NAME 'enhancedSearchGuide' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.21 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.23 + NAME 'facsimileTelephoneNumber' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.22 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.44 + NAME 'generationQualifier' + SUP name + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.42 + NAME 'givenName' + SUP name + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.51 + NAME 'houseIdentifier' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.43 + NAME 'initials' + SUP name + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.25 + NAME 'internationalISDNNumber' + EQUALITY numericStringMatch + SUBSTR numericStringSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.7 + NAME 'l' + SUP name + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.31 + NAME 'member' + SUP distinguishedName + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.10 + NAME 'o' + SUP name + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.11 + NAME 'ou' + SUP name + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.32 + NAME 'owner' + SUP distinguishedName + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.19 + NAME 'physicalDeliveryOfficeName' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.16 + NAME 'postalAddress' + EQUALITY caseIgnoreListMatch + SUBSTR caseIgnoreListSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.17 + NAME 'postalCode' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.18 + NAME 'postOfficeBox' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.28 + NAME 'preferredDeliveryMethod' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.14 + SINGLE-VALUE + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.26 + NAME 'registeredAddress' + SUP postalAddress + SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.33 + NAME 'roleOccupant' + SUP distinguishedName + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.14 + NAME 'searchGuide' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.25 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.34 + NAME 'seeAlso' + SUP distinguishedName + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.5 + NAME 'serialNumber' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.4 + NAME 'sn' + SUP name + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.8 + NAME 'st' + SUP name + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.9 + NAME 'street' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.20 + NAME 'telephoneNumber' + EQUALITY telephoneNumberMatch + SUBSTR telephoneNumberSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.22 + NAME 'teletexTerminalIdentifier' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.51 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.21 + NAME 'telexNumber' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.52 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.12 + NAME 'title' + SUP name + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 0.9.2342.19200300.100.1.1 + NAME 'uid' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.50 + NAME 'uniqueMember' + EQUALITY uniqueMemberMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.34 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.35 + NAME 'userPassword' + EQUALITY octetStringMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 3.1.1.3.1.5.1 + NAME 'unicodePwd' + EQUALITY octetStringMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.24 + NAME 'x121Address' + EQUALITY numericStringMatch + SUBSTR numericStringSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.24 + NAME 'creationDate' + EQUALITY numericStringMatch + SUBSTR numericStringSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.5.4.45 + NAME 'x500UniqueIdentifier' + EQUALITY bitStringMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.6 + X-ORIGIN 'RFC 4519' ) +attributeTypes: ( 2.16.840.1.113730.3.1.1 + NAME 'carLicense' + DESC 'vehicle license or registration plate' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 2798' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2 + NAME 'departmentNumber' + DESC 'identifies a department within an organization' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 2798' ) +attributeTypes: ( 2.16.840.1.113730.3.1.241 + NAME 'displayName' + DESC 'preferred name of a person to be used when displaying entries' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + SINGLE-VALUE + X-ORIGIN 'RFC 2798' ) +attributeTypes: ( 2.16.840.1.113730.3.1.3 + NAME 'employeeNumber' + DESC 'numerically identifies an employee within an organization' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + SINGLE-VALUE + X-ORIGIN 'RFC 2798' ) +attributeTypes: ( 2.13.840.1.113730.3.1.3 + NAME 'userAccountControl' + DESC 'the user account control' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.1.6.1.4.1.1466.115.121.1.15 + SINGLE-VALUE + X-ORIGIN 'RFC 2798' ) +attributeTypes: ( 2.16.840.1.113730.3.1.4 + NAME 'employeeType' + DESC 'type of employment for a person' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 2798' ) +attributeTypes: ( 0.9.2342.19200300.100.1.60 + NAME 'jpegPhoto' + DESC 'a JPEG image' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.28 + X-ORIGIN 'RFC 2798' ) +attributeTypes: ( 2.16.840.1.113730.3.1.39 + NAME 'preferredLanguage' + DESC 'preferred written or spoken language for a person' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + SINGLE-VALUE + X-ORIGIN 'RFC 2798' ) +attributeTypes: ( 2.16.840.1.113730.3.1.40 + NAME 'userSMIMECertificate' + DESC 'PKCS#7 SignedData used to support S/MIME' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 + X-ORIGIN 'RFC 2798' ) +attributeTypes: ( 2.16.840.1.113730.3.1.216 + NAME 'userPKCS12' + DESC 'PKCS #12 PFX PDU for exchange of personal identity information' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 + X-ORIGIN 'RFC 2798' ) +attributeTypes: ( 2.5.4.36 + NAME 'userCertificate' + DESC 'X.509 user certificate' + EQUALITY certificateExactMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 + X-ORIGIN 'RFC 4523' ) +attributeTypes: ( 2.5.4.37 + NAME 'cACertificate' + DESC 'X.509 CA certificate' + EQUALITY certificateExactMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 + X-ORIGIN 'RFC 4523' ) +attributeTypes: ( 2.5.4.40 + NAME 'crossCertificatePair' + DESC 'X.509 cross certificate pair' + EQUALITY certificatePairExactMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.10 + X-ORIGIN 'RFC 4523' ) +attributeTypes: ( 2.5.4.39 + NAME 'certificateRevocationList' + DESC 'X.509 certificate revocation list' + EQUALITY certificateListExactMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 + X-ORIGIN 'RFC 4523' ) +attributeTypes: ( 2.5.4.38 + NAME 'authorityRevocationList' + DESC 'X.509 authority revocation list' + EQUALITY certificateListExactMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 + X-ORIGIN 'RFC 4523' ) +attributeTypes: ( 2.5.4.53 + NAME 'deltaRevocationList' + DESC 'X.509 delta revocation list' + EQUALITY certificateListExactMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 + X-ORIGIN 'RFC 4523' ) +attributeTypes: ( 2.5.4.52 + NAME 'supportedAlgorithms' + DESC 'X.509 supported algorithms' + EQUALITY algorithmIdentifierMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.49 + X-ORIGIN 'RFC 4523' ) +attributeTypes: ( 0.9.2342.19200300.100.1.37 + NAME 'associatedDomain' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.38 + NAME 'associatedName' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.48 + NAME 'buildingName' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.43 + NAME 'co' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.14 + NAME 'documentAuthor' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.11 + NAME 'documentIdentifier' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.15 + NAME 'documentLocation' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.56 + NAME 'documentPublisher' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.12 + NAME 'documentTitle' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.13 + NAME 'documentVersion' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.5 + NAME 'drink' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.20 + NAME 'homePhone' + EQUALITY telephoneNumberMatch + SUBSTR telephoneNumberSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.39 + NAME 'homePostalAddress' + EQUALITY caseIgnoreListMatch + SUBSTR caseIgnoreListSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.9 + NAME 'host' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.4 + NAME 'info' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{2048} + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.3 + NAME 'mail' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.10 + NAME 'manager' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.41 + NAME 'mobile' + EQUALITY telephoneNumberMatch + SUBSTR telephoneNumberSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.45 + NAME 'organizationalStatus' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.42 + NAME 'pager' + EQUALITY telephoneNumberMatch + SUBSTR telephoneNumberSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.40 + NAME 'personalTitle' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.6 + NAME 'roomNumber' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.6 + NAME 'sAMAccountName' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.110.1.6 + NAME 'distinguishedName' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.9.1.4.1.1466.115.121.1.15{256} + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.6 + NAME 'userPrincipalName' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.21 + NAME 'secretary' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.44 + NAME 'uniqueIdentifier' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.8 + NAME 'userClass' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} + X-ORIGIN 'RFC 4524' ) +attributeTypes: ( 0.9.2342.19200300.100.1.55 + NAME 'audio' + EQUALITY octetStringMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{250000} + X-ORIGIN 'RFC 2798' ) +attributeTypes: ( 0.9.2342.19200300.100.1.7 + NAME 'photo' + X-ORIGIN 'RFC 2798' ) +attributeTypes: ( 1.3.6.1.4.1.250.1.57 + NAME 'labeledURI' + EQUALITY caseExactMatch + SUBSTR caseExactSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN 'RFC 2798' ) +attributeTypes: ( 1.3.6.1.1.20 + NAME 'entryDN' + DESC 'DN of the entry' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + SINGLE-VALUE + NO-USER-MODIFICATION + USAGE directoryOperation + X-ORIGIN 'RFC 5020' ) +attributeTypes: ( 2.16.840.1.113730.3.1.34 + NAME 'ref' + DESC 'named reference - a labeledURI' + EQUALITY caseExactMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + USAGE distributedOperation + X-ORIGIN 'RFC 3296' ) +attributeTypes: ( 1.3.6.1.1.4 + NAME 'vendorName' + EQUALITY 1.3.6.1.4.1.1466.109.114.1 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + SINGLE-VALUE + NO-USER-MODIFICATION + USAGE dSAOperation + X-ORIGIN 'RFC 3045' ) +attributeTypes: ( 1.3.6.1.1.5 + NAME 'vendorVersion' + EQUALITY 1.3.6.1.4.1.1466.109.114.1 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + SINGLE-VALUE + NO-USER-MODIFICATION + USAGE dSAOperation + X-ORIGIN 'RFC 3045' ) +attributeTypes: ( 1.3.6.1.1.16.4 + NAME 'entryUUID' + DESC 'UUID of the entry' + EQUALITY uuidMatch + ORDERING uuidOrderingMatch + SYNTAX 1.3.6.1.1.16.1 + SINGLE-VALUE + NO-USER-MODIFICATION + USAGE directoryOperation + X-ORIGIN 'RFC 4530' ) +attributeTypes: ( 1.3.6.1.4.1.453.16.2.103 + NAME 'numSubordinates' + DESC 'count of immediate subordinates' + EQUALITY integerMatch + ORDERING integerOrderingMatch + SYNTAX 1.3.6.1.4.1.453.16.2.103 + SINGLE-VALUE + NO-USER-MODIFICATION + USAGE directoryOperation + X-ORIGIN 'draft-ietf-boreham-numsubordinates' ) +attributeTypes: ( 1.3.6.1.4.1.7628.5.4.1 + NAME 'inheritable' + SYNTAX BOOLEAN + SINGLE-VALUE + NO-USER-MODIFICATION + USAGE dSAOperation + X-ORIGIN 'draft-ietf-ldup-subentry' ) +attributeTypes: ( 1.3.6.1.4.1.7628.5.4.2 + NAME 'blockInheritance' + SYNTAX BOOLEAN + SINGLE-VALUE + NO-USER-MODIFICATION + USAGE dSAOperation + X-ORIGIN 'draft-ietf-ldup-subentry' ) +attributeTypes: ( 2.16.840.1.113730.3.1.5 + NAME 'changeNumber' + DESC 'a number which uniquely identifies a change made to a directory entry' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + EQUALITY integerMatch + ORDERING integerOrderingMatch + SINGLE-VALUE + X-ORIGIN 'draft-good-ldap-changelog' ) +attributeTypes: ( 2.16.840.1.113730.3.1.6 + NAME 'targetDN' + DESC 'the DN of the entry which was modified' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + SINGLE-VALUE + X-ORIGIN 'draft-good-ldap-changelog' ) +attributeTypes: ( 2.16.840.1.113730.3.1.7 + NAME 'changeType' + DESC 'the type of change made to an entry' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + SINGLE-VALUE + X-ORIGIN 'draft-good-ldap-changelog' ) +attributeTypes: ( 2.16.840.1.113730.3.1.8 + NAME 'changes' + DESC 'a set of changes to apply to an entry' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 + X-ORIGIN 'draft-good-ldap-changelog' ) +attributeTypes: ( 2.16.840.1.113730.3.1.9 + NAME 'newRDN' + DESC 'the new RDN of an entry which is the target of a modrdn operation' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + SINGLE-VALUE + X-ORIGIN 'draft-good-ldap-changelog' ) +attributeTypes: ( 2.16.840.1.113730.3.1.10 + NAME 'deleteOldRDN' + DESC 'a flag which indicates if the old RDN should be retained as an + attribute of the entry' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + SINGLE-VALUE + X-ORIGIN 'draft-good-ldap-changelog' ) +attributeTypes: ( 2.16.840.1.113730.3.1.11 + NAME 'newSuperior' + DESC 'the new parent of an entry which is the target of a moddn operation' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + SINGLE-VALUE + X-ORIGIN 'draft-good-ldap-changelog' ) +attributeTypes: ( 2.16.840.1.113730.3.1.35 + NAME 'changelog' + DESC 'the distinguished name of the entry which contains the set of entries + comprising the server changelog' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN 'draft-good-ldap-changelog' ) +attributeTypes: ( 1.3.6.1.4.1.4203.1.3.3 + NAME 'supportedAuthPasswordSchemes' + DESC 'supported password storage schemes' + EQUALITY caseExactIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} + USAGE dSAOperation + X-ORIGIN 'RFC 3112' ) +attributeTypes: ( 1.3.6.1.4.1.4203.1.3.4 + NAME 'authPassword' + DESC 'password authentication information' + EQUALITY 1.3.6.1.4.1.4203.1.2.2 + SYNTAX 1.3.6.1.4.1.4203.1.1.2 + X-ORIGIN 'RFC 3112' ) +attributeTypes: ( 2.16.840.1.113730.3.1.55 + NAME 'aci' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + USAGE directoryOperation + X-ORIGIN 'De facto standard' ) +objectClasses: ( 2.5.6.0 + NAME 'top' + ABSTRACT + MUST objectClass + X-ORIGIN 'RFC 4512' ) +objectClasses: ( 2.5.6.1 + NAME 'alias' + SUP top + STRUCTURAL + MUST aliasedObjectName + X-ORIGIN 'RFC 4512' ) +objectClasses: ( 1.3.6.1.4.1.1466.101.120.111 + NAME 'extensibleObject' + SUP top + AUXILIARY + X-ORIGIN 'RFC 4512' ) +objectClasses: ( 2.5.20.1 + NAME 'subschema' + AUXILIARY + MAY ( dITStructureRules $ + nameForms $ + ditContentRules $ + objectClasses $ + attributeTypes $ + matchingRules $ + matchingRuleUse ) ) +objectClasses: ( 2.5.6.11 + NAME 'applicationProcess' + SUP top + STRUCTURAL + MUST cn + MAY ( seeAlso $ + ou $ + l $ + description ) + X-ORIGIN 'RFC 4519' ) +objectClasses: ( 2.5.6.2 + NAME 'country' + SUP top + STRUCTURAL + MUST c + MAY ( searchGuide $ + description ) + X-ORIGIN 'RFC 4519' ) +objectClasses: ( 1.3.6.1.4.1.1466.344 + NAME 'dcObject' + SUP top + AUXILIARY + MUST dc + X-ORIGIN 'RFC 4519' ) +objectClasses: ( 2.5.6.14 + NAME 'device' + SUP top + STRUCTURAL + MUST cn + MAY ( serialNumber $ + seeAlso $ + owner $ + ou $ + o $ + l $ + description ) + X-ORIGIN 'RFC 4519' ) +objectClasses: ( 2.5.6.9 + NAME 'groupOfNames' + SUP top + STRUCTURAL + MUST cn + MAY ( member $ + businessCategory $ + seeAlso $ + owner $ + ou $ + o $ + description ) + X-ORIGIN 'RFC 4519' ) +objectClasses: ( 2.5.6.17 + NAME 'groupOfUniqueNames' + SUP top + STRUCTURAL + MUST cn + MAY ( uniqueMember $ + businessCategory $ + seeAlso $ + owner $ + ou $ + o $ + description ) + X-ORIGIN 'RFC 4519' ) +objectClasses: ( 2.5.6.3 + NAME 'locality' + SUP top + STRUCTURAL + MAY ( street $ + seeAlso $ + searchGuide $ + st $ + l $ + description ) + X-ORIGIN 'RFC 4519' ) +objectClasses: ( 2.5.6.4 + NAME 'organization' + SUP top + STRUCTURAL + MUST o + MAY ( userPassword $ + searchGuide $ + seeAlso $ + businessCategory $ + x121Address $ + registeredAddress $ + destinationIndicator $ + preferredDeliveryMethod $ + telexNumber $ + teletexTerminalIdentifier $ + telephoneNumber $ + internationalISDNNumber $ + facsimileTelephoneNumber $ + street $ + postOfficeBox $ + postalCode $ + postalAddress $ + physicalDeliveryOfficeName $ + st $ + l $ + description ) + X-ORIGIN 'RFC 4519' ) +objectClasses: ( 2.5.6.6 + NAME 'person' + SUP top + STRUCTURAL + MUST ( sn $ + cn ) + MAY ( userPassword $ + telephoneNumber $ + creationDate $ + seeAlso $ + description ) + X-ORIGIN 'RFC 4519' ) +objectClasses: ( 2.5.6.7 + NAME 'organizationalPerson' + SUP person + STRUCTURAL + MAY ( title $ + x121Address $ + registeredAddress $ + destinationIndicator $ + preferredDeliveryMethod $ + telexNumber $ + teletexTerminalIdentifier $ + telephoneNumber $ + internationalISDNNumber $ + facsimileTelephoneNumber $ + street $ + postOfficeBox $ + postalCode $ + postalAddress $ + physicalDeliveryOfficeName $ + ou $ + st $ + l ) + X-ORIGIN 'RFC 4519' ) +objectClasses: ( 2.5.6.8 + NAME 'organizationalRole' + SUP top + STRUCTURAL + MUST cn + MAY ( x121Address $ + registeredAddress $ + destinationIndicator $ + preferredDeliveryMethod $ + telexNumber $ + teletexTerminalIdentifier $ + telephoneNumber $ + internationalISDNNumber $ + facsimileTelephoneNumber $ + seeAlso $ + roleOccupant $ + preferredDeliveryMethod $ + street $ + postOfficeBox $ + postalCode $ + postalAddress $ + physicalDeliveryOfficeName $ + ou $ + st $ + l $ + description ) + X-ORIGIN 'RFC 4519' ) +objectClasses: ( 2.5.6.5 + NAME 'organizationalUnit' + SUP top + STRUCTURAL + MUST ou + MAY ( businessCategory $ + description $ + destinationIndicator $ + facsimileTelephoneNumber $ + internationalISDNNumber $ + l $ + physicalDeliveryOfficeName $ + postalAddress $ + postalCode $ + postOfficeBox $ + preferredDeliveryMethod $ + registeredAddress $ + searchGuide $ + seeAlso $ + st $ + street $ + telephoneNumber $ + teletexTerminalIdentifier $ + telexNumber $ + userPassword $ + x121Address ) + X-ORIGIN 'RFC 4519' ) +objectClasses: ( 2.5.6.10 + NAME 'residentialPerson' + SUP person + STRUCTURAL + MUST l + MAY ( businessCategory $ + x121Address $ + registeredAddress $ + destinationIndicator $ + preferredDeliveryMethod $ + telexNumber $ + teletexTerminalIdentifier $ + telephoneNumber $ + internationalISDNNumber $ + facsimileTelephoneNumber $ + preferredDeliveryMethod $ + street $ + postOfficeBox $ + postalCode $ + postalAddress $ + physicalDeliveryOfficeName $ + st $ + l ) + X-ORIGIN 'RFC 4519' ) +objectClasses: ( 1.3.6.1.1.3.1 + NAME 'uidObject' + SUP top + AUXILIARY + MUST uid + X-ORIGIN 'RFC 4519' ) +objectClasses: ( 2.16.840.1.113730.3.2.2 + NAME 'inetOrgPerson' + SUP organizationalPerson + STRUCTURAL + MAY ( audio $ + businessCategory $ + carLicense $ + departmentNumber $ + displayName $ + employeeNumber $ + employeeType $ + givenName $ + homePhone $ + homePostalAddress $ + initials $ + jpegPhoto $ + labeledURI $ + mail $ + manager $ + mobile $ + o $ + pager $ + photo $ + roomNumber $ + secretary $ + uid $ + userCertificate $ + x500uniqueIdentifier $ + preferredLanguage $ + userSMIMECertificate $ + userPKCS12 ) + X-ORIGIN 'RFC 2798' ) +objectClasses: ( 2.5.6.21 + NAME 'pkiUser' + DESC 'X.509 PKI User' + SUP top AUXILIARY + MAY userCertificate + X-ORIGIN 'RFC 4523' ) +objectClasses: ( 2.5.6.22 + NAME 'pkiCA' + DESC 'X.509 PKI Certificate Authority' + SUP top + AUXILIARY + MAY ( cACertificate $ + certificateRevocationList $ + authorityRevocationList $ + crossCertificatePair ) + X-ORIGIN 'RFC 4523' ) +objectClasses: ( 2.5.6.19 + NAME 'cRLDistributionPoint' + DESC 'X.509 CRL distribution point' + SUP top + STRUCTURAL + MUST cn + MAY ( certificateRevocationList $ + authorityRevocationList $ + deltaRevocationList ) + X-ORIGIN 'RFC 4523' ) +objectClasses: ( 2.5.6.23 + NAME 'deltaCRL' + DESC 'X.509 delta CRL' + SUP top + AUXILIARY + MAY deltaRevocationList + X-ORIGIN 'RFC 4523' ) +objectClasses: ( 2.5.6.15 + NAME 'strongAuthenticationUser' + DESC 'X.521 strong authentication user' + SUP top + AUXILIARY + MUST userCertificate + X-ORIGIN 'RFC 4523' ) +objectClasses: ( 2.5.6.18 + NAME 'userSecurityInformation' + DESC 'X.521 user security information' + SUP top + AUXILIARY + MAY ( supportedAlgorithms ) + X-ORIGIN 'RFC 4523' ) +objectClasses: ( 2.5.6.16 + NAME 'certificationAuthority' + DESC 'X.509 certificate authority' + SUP top + AUXILIARY + MUST ( authorityRevocationList $ + certificateRevocationList $ + cACertificate ) + MAY crossCertificatePair + X-ORIGIN 'RFC 4523' ) +objectClasses: ( 2.5.6.16.2 + NAME 'certificationAuthority-V2' + DESC 'X.509 certificate authority, version 2' + SUP certificationAuthority + AUXILIARY + MAY deltaRevocationList + X-ORIGIN 'RFC 4523' ) +objectClasses: ( 0.9.2342.19200300.100.4.5 + NAME 'account' + SUP top STRUCTURAL + MUST uid + MAY ( description $ + seeAlso $ + l $ + o $ + ou $ + host ) + X-ORIGIN 'RFC 4524' ) +objectClasses: ( 0.9.2342.19200300.100.4.6 + NAME 'document' + SUP top STRUCTURAL + MUST documentIdentifier + MAY ( cn $ + description $ + seeAlso $ + l $ + o $ + ou $ + documentTitle $ + documentVersion $ + documentAuthor $ + documentLocation $ + documentPublisher ) + X-ORIGIN 'RFC 4524' ) +objectClasses: ( 0.9.2342.19200300.100.4.9 + NAME 'documentSeries' + SUP top STRUCTURAL + MUST cn + MAY ( description $ + l $ + o $ + ou $ + seeAlso $ + telephonenumber ) + X-ORIGIN 'RFC 4524' ) +objectClasses: ( 0.9.2342.19200300.100.4.13 + NAME 'domain' + SUP top + STRUCTURAL + MUST dc + MAY ( userPassword $ + searchGuide $ + seeAlso $ + businessCategory $ + x121Address $ + registeredAddress $ + destinationIndicator $ + preferredDeliveryMethod $ + telexNumber $ + teletexTerminalIdentifier $ + telephoneNumber $ + internationaliSDNNumber $ + facsimileTelephoneNumber $ + street $ + postOfficeBox $ + postalCode $ + postalAddress $ + physicalDeliveryOfficeName $ + st $ + l $ + description $ + o $ + associatedName ) + X-ORIGIN 'RFC 4524' ) +objectClasses: ( 0.9.2342.19200300.100.4.17 + NAME 'domainRelatedObject' + SUP top + AUXILIARY + MUST associatedDomain + X-ORIGIN 'RFC 4524' ) +objectClasses: ( 0.9.2342.19200300.100.4.18 + NAME 'friendlyCountry' + SUP country + STRUCTURAL + MUST co + X-ORIGIN 'RFC 4524' ) +objectClasses: ( 0.9.2342.19200300.100.4.14 + NAME 'rFC822localPart' + SUP domain + STRUCTURAL + MAY ( cn $ + description $ + destinationIndicator $ + facsimileTelephoneNumber $ + internationaliSDNNumber $ + physicalDeliveryOfficeName $ + postalAddress $ + postalCode $ + postOfficeBox $ + preferredDeliveryMethod $ + registeredAddress $ + seeAlso $ + sn $ + street $ + telephoneNumber $ + teletexTerminalIdentifier $ + telexNumber $ + x121Address ) + X-ORIGIN 'RFC 4524' ) +objectClasses: ( 0.9.2342.19211300.100.4.7 + NAME 'room' + SUP top + STRUCTURAL + MUST cn + MAY ( roomNumber $ + description $ + seeAlso $ + telephoneNumber ) + X-ORIGIN 'RFC 4524' ) +objectClasses: ( 0.9.2342.19211300.100.4.7 + NAME 'ad' + SUP top + STRUCTURAL + MUST ( sAMAccountName $ + unicodePwd $ + userAccountControl $ + distinguishedName ) + MAY ( userPrincipalName $ + description ) + X-ORIGIN 'RFC 4524' ) +objectClasses: ( 0.9.2342.19200300.100.4.19 + NAME 'simpleSecurityObject' + SUP top + AUXILIARY + MUST userPassword + X-ORIGIN 'RFC 4524' ) +objectClasses: ( 2.16.840.1.113730.3.2.6 + NAME 'referral' + DESC 'named subordinate reference object' + STRUCTURAL + MUST ref + X-ORIGIN 'RFC 3296' ) +objectClasses: ( 1.3.6.1.4.1.5322.13.1.1 + NAME 'namedObject' + SUP top + STRUCTURAL MAY cn + X-ORIGIN 'draft-howard-namedobject' ) +objectClasses: ( 2.16.840.1.113719.2.142.6.1.1 + NAME 'ldapSubEntry' + DESC 'LDAP Subentry class, version 1' + SUP top + STRUCTURAL + MAY ( cn ) + X-ORIGIN 'draft-ietf-ldup-subentry' ) +objectClasses: ( 1.3.6.1.4.1.7628.5.6.1.1 + NAME 'inheritableLDAPSubEntry' + DESC 'Inheritable LDAP Subentry class, version 1' + SUP ldapSubEntry + STRUCTURAL + MUST ( inheritable ) + MAY ( blockInheritance ) + X-ORIGIN 'draft-ietf-ldup-subentry' ) +objectClasses: ( 2.16.840.1.113730.3.2.1 + NAME 'changeLogEntry' + SUP top + STRUCTURAL + MUST ( changeNumber $ + targetDN $ + changeType ) + MAY ( changes $ + newRDN $ + deleteOldRDN $ + newSuperior ) + X-ORIGIN 'draft-good-ldap-changelog' ) +objectClasses: ( 1.3.6.1.4.1.4203.1.4.7 + NAME 'authPasswordObject' + DESC 'authentication password mix in class' + AUXILIARY + MAY authPassword + X-ORIGIN 'RFC 3112' ) +############################## +# Registered Service Schema +############################## +objectClasses: ( 1.6.7.9.0.1.4563.1.4.7 + NAME 'casRegisteredService' + SUP top + STRUCTURAL + MUST ( uid $ + description ) + X-ORIGIN 'RFC 3112' ) +############################## +# User Service Details Schema +############################## +objectClasses: ( 1.6.7.9.0.1.4563.1.4.7 + NAME 'casServiceUserDetails' + SUP top + STRUCTURAL + MUST ( uid $ + contactPerson $ + member $ + creationDate $ + uugid ) + X-ORIGIN 'RFC 3112' ) diff --git a/cas-server-3.4.2/cas-server-support-jdbc/.cvsignore b/cas-server-support-legacy/.cvsignore similarity index 100% rename from cas-server-3.4.2/cas-server-support-jdbc/.cvsignore rename to cas-server-support-legacy/.cvsignore diff --git a/cas-server-support-legacy/NOTICE b/cas-server-support-legacy/NOTICE new file mode 100644 index 000000000000..c2e4711000f1 --- /dev/null +++ b/cas-server-support-legacy/NOTICE @@ -0,0 +1,103 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS Legacy Support under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + CDI APIs under Apache License, Version 2.0 + Central Authentication Service under Jasig License + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + servlet-api under Commons Development and Distribution License, Version 1.0 + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-support-legacy/pom.xml b/cas-server-support-legacy/pom.xml new file mode 100644 index 000000000000..5a4e2fd75acb --- /dev/null +++ b/cas-server-support-legacy/pom.xml @@ -0,0 +1,49 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-support-legacy + jar + Apereo CAS Legacy Support + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + cas + cas + 2.0.12 + + + + + ${project.parent.basedir} + + diff --git a/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/CredentialsAdapter.java b/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/CredentialsAdapter.java new file mode 100644 index 000000000000..409dbc49d6b0 --- /dev/null +++ b/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/CredentialsAdapter.java @@ -0,0 +1,38 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.authentication.principal.Credentials; + +/** + * Adapts a CAS 4.0 {@link Credential} onto a CAS 3.x {@link Credentials}. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public interface CredentialsAdapter { + + /** + * Converts a CAS 4.0 credential to a CAS 3.0 credential. + * + * @param credential the credential + * @return CAS 3.0 credential. + */ + Credentials convert(Credential credential); +} diff --git a/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/LegacyAuthenticationHandlerAdapter.java b/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/LegacyAuthenticationHandlerAdapter.java new file mode 100644 index 000000000000..72e125242514 --- /dev/null +++ b/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/LegacyAuthenticationHandlerAdapter.java @@ -0,0 +1,108 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.authentication.handler.NamedAuthenticationHandler; +import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; + +import javax.security.auth.login.FailedLoginException; +import javax.validation.constraints.NotNull; +import java.security.GeneralSecurityException; + +/** + * Adapts a CAS 3.x {@link org.jasig.cas.authentication.handler.AuthenticationHandler} onto a CAS 4.x + * {@link AuthenticationHandler}. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class LegacyAuthenticationHandlerAdapter implements AuthenticationHandler { + + /** Wrapped CAS 3.0 authentication handler. */ + @NotNull + private final org.jasig.cas.authentication.handler.AuthenticationHandler legacyHandler; + + /** Adapts CAS 4.0 credentials onto CAS 3.0 credentials. */ + @NotNull + private final CredentialsAdapter credentialsAdapter; + + + /** + * Creates a new instance that adapts the given legacy authentication handler. + * + * @param legacy CAS 3.0 authentication handler. + */ + public LegacyAuthenticationHandlerAdapter(final org.jasig.cas.authentication.handler.AuthenticationHandler legacy) { + if (!legacy.supports(new UsernamePasswordCredentials())) { + throw new IllegalArgumentException( + "Cannot infer credential conversion strategy - specify CredentialsAdapter explicitly."); + } + this.legacyHandler = legacy; + this.credentialsAdapter = new UsernamePasswordCredentialsAdapter(); + } + + /** + * Creates a new instance that adapts the given legacy authentication handler. + * Use this form for a handler that supports a credential type other than username/password credentials. + * + * @param legacy CAS 3.0 authentication handler. + * @param adapter Adapts CAS 4.0 credential onto 3.0 credential. + */ + public LegacyAuthenticationHandlerAdapter( + final org.jasig.cas.authentication.handler.AuthenticationHandler legacy, + final CredentialsAdapter adapter) { + this.legacyHandler = legacy; + this.credentialsAdapter = adapter; + } + + @Override + public HandlerResult authenticate(final Credential credential) throws GeneralSecurityException, PreventedException { + try { + if (this.legacyHandler.authenticate(credentialsAdapter.convert(credential))) { + final CredentialMetaData md; + if (credential instanceof CredentialMetaData) { + md = (CredentialMetaData) credential; + } else { + md = new BasicCredentialMetaData(credential); + } + return new DefaultHandlerResult(this, md); + } else { + throw new FailedLoginException( + String.format("%s failed to authenticate %s", this.getName(), credential)); + } + } catch (final org.jasig.cas.authentication.handler.AuthenticationException e) { + throw new GeneralSecurityException( + String.format("%s failed to authenticate %s", this.getName(), credential), e); + } + } + + @Override + public boolean supports(final Credential credential) { + return this.legacyHandler.supports(credentialsAdapter.convert(credential)); + } + + @Override + public String getName() { + if (this.legacyHandler instanceof NamedAuthenticationHandler) { + return ((NamedAuthenticationHandler) this.legacyHandler).getName(); + } else { + return this.legacyHandler.getClass().getSimpleName(); + } + } +} diff --git a/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/UsernamePasswordCredentialsAdapter.java b/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/UsernamePasswordCredentialsAdapter.java new file mode 100644 index 000000000000..b79504e7a173 --- /dev/null +++ b/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/UsernamePasswordCredentialsAdapter.java @@ -0,0 +1,42 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; + +/** + * Converts a CAS 4.0 username/password credential into a CAS 3.0 username/password credential. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class UsernamePasswordCredentialsAdapter implements CredentialsAdapter { + @Override + public Credentials convert(final Credential credential) { + if (!(credential instanceof UsernamePasswordCredential)) { + throw new IllegalArgumentException(credential + " not supported."); + } + final UsernamePasswordCredential original = (UsernamePasswordCredential) credential; + final UsernamePasswordCredentials old = new UsernamePasswordCredentials(); + old.setUsername(original.getUsername()); + old.setPassword(original.getPassword()); + return old; + } +} diff --git a/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/handler/AuthenticationHandler.java b/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/handler/AuthenticationHandler.java new file mode 100644 index 000000000000..846d0caab0e6 --- /dev/null +++ b/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/handler/AuthenticationHandler.java @@ -0,0 +1,68 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +import org.jasig.cas.authentication.principal.Credentials; + +/** + * Deprecated interface for authenticating user-supplied credential. This component has + * been superseded by {@link org.jasig.cas.authentication.AuthenticationHandler} as of + * CAS 4.0. + *

+ * Validate Credential support for AuthenticationManagerImpl. + *

+ * Determines that Credential are valid. Password-based credential may be + * tested against an external LDAP, Kerberos, JDBC source. Certificates may be + * checked against a list of CA's and do the usual chain validation. + * Implementations must be parameterized with their sources of information. + *

+ * Callers to this class should first call supports to determine if the + * AuthenticationHandler can authenticate the credential provided. + * + * @author Scott Battaglia + * @deprecated In favor of {@link org.jasig.cas.authentication.AuthenticationHandler}. + * @since 3.0.0 + * @see org.jasig.cas.authentication.LegacyAuthenticationHandlerAdapter + * @see org.jasig.cas.authentication.AuthenticationHandler + */ +@Deprecated +public interface AuthenticationHandler { + + /** + * Method to determine if the credential supplied are valid. + * + * @param credential The credential to validate. + * @return true if valid, return false otherwise. + * @throws AuthenticationException An AuthenticationException can contain + * details about why a particular authentication request failed. + */ + boolean authenticate(Credentials credential) + throws AuthenticationException; + + /** + * Method to check if the handler knows how to handle the credential + * provided. It may be a simple check of the Credential class or something + * more complicated such as scanning the information contained in the + * Credential object. + * + * @param credential The credential to check. + * @return true if the handler supports the Credential, false othewrise. + */ + boolean supports(Credentials credential); +} diff --git a/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/handler/NamedAuthenticationHandler.java b/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/handler/NamedAuthenticationHandler.java new file mode 100644 index 000000000000..9ea06d860858 --- /dev/null +++ b/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/handler/NamedAuthenticationHandler.java @@ -0,0 +1,40 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +/** + * Named variant of CAS 3.0 {@link AuthenticationHandler} interface. This is deprecated in favor of + * {@link org.jasig.cas.authentication.AuthenticationHandler}. + * + * @author Scott Battaglia + * @deprecated The CAS 4.0 {@link org.jasig.cas.authentication.AuthenticationHandler} provides support for named + * handlers, which makes this interface redundant. + * @since 3.2.1 + * + */ +@Deprecated +public interface NamedAuthenticationHandler extends AuthenticationHandler { + + /** + * Gets the name of this handler. + * + * @return the name + */ + String getName(); +} diff --git a/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/principal/Credentials.java b/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/principal/Credentials.java new file mode 100644 index 000000000000..555410232fc9 --- /dev/null +++ b/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/principal/Credentials.java @@ -0,0 +1,31 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import java.io.Serializable; + +/** + * Deprecated credential marker interface provides transition path from CAS 3.x to 4.x authentication components. + * + * @author Marvin S. Addison + * @deprecated This component has been renamed to {@link org.jasig.cas.authentication.Credential} in CAS 4.0. + * @since 4.0.0 + */ +@Deprecated +public interface Credentials extends Serializable {} diff --git a/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/principal/UsernamePasswordCredentials.java b/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/principal/UsernamePasswordCredentials.java new file mode 100644 index 000000000000..8aca083b4944 --- /dev/null +++ b/cas-server-support-legacy/src/main/java/org/jasig/cas/authentication/principal/UsernamePasswordCredentials.java @@ -0,0 +1,114 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.principal; + +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +/** + * UsernamePasswordCredentials respresents the username and password that a user + * may provide in order to prove the authenticity of who they say they are. + * + * @author Scott Battaglia + * @since 3.0.0 + *

+ * This is a published and supported CAS Server 3 API. + *

+ */ +public class UsernamePasswordCredentials implements Credentials { + + /** Unique ID for serialization. */ + private static final long serialVersionUID = -8343864967200862794L; + + /** The username. */ + @NotNull + @Size(min=1, message = "required.username") + private String username; + + /** The password. */ + @NotNull + @Size(min=1, message = "required.password") + private String password; + + /** + * @return Returns the password. + */ + public final String getPassword() { + return this.password; + } + + /** + * @param password The password to set. + */ + public final void setPassword(final String password) { + this.password = password; + } + + /** + * @return Returns the userName. + */ + public final String getUsername() { + return this.username; + } + + /** + * @param userName The userName to set. + */ + public final void setUsername(final String userName) { + this.username = userName; + } + + @Override + public String toString() { + return "[username: " + this.username + ']'; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final UsernamePasswordCredentials that = (UsernamePasswordCredentials) o; + + if (password != null ? !password.equals(that.password) : that.password != null) { + return false; + } + + if (username != null ? !username.equals(that.username) : that.username != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return new HashCodeBuilder() + .append(username) + .append(password) + .toHashCode(); + + } +} diff --git a/cas-server-support-legacy/src/site/site.xml b/cas-server-support-legacy/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-support-legacy/src/site/site.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/cas-server-3.4.2/cas-server-support-ldap/src/test/clover/clover.license b/cas-server-support-legacy/src/test/clover/clover.license similarity index 100% rename from cas-server-3.4.2/cas-server-support-ldap/src/test/clover/clover.license rename to cas-server-support-legacy/src/test/clover/clover.license diff --git a/cas-server-support-legacy/src/test/java/org/jasig/cas/authentication/LegacyAuthenticationHandlerAdapterTest.java b/cas-server-support-legacy/src/test/java/org/jasig/cas/authentication/LegacyAuthenticationHandlerAdapterTest.java new file mode 100644 index 000000000000..a51806ab4bad --- /dev/null +++ b/cas-server-support-legacy/src/test/java/org/jasig/cas/authentication/LegacyAuthenticationHandlerAdapterTest.java @@ -0,0 +1,73 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication; + +import javax.security.auth.login.FailedLoginException; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Unit test for {@link LegacyAuthenticationHandlerAdapter}. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "/test-context.xml" }) +public class LegacyAuthenticationHandlerAdapterTest { + + @Autowired + @Qualifier("alwaysPassHandler") + private AuthenticationHandler alwaysPassHandler; + + @Autowired + @Qualifier("alwaysFailHandler") + private AuthenticationHandler alwaysFailHandler; + + @Test + public void verifyAuthenticateSuccess() throws Exception { + final HandlerResult result = alwaysPassHandler.authenticate(new UsernamePasswordCredential("a", "b")); + assertEquals("TestAlwaysPassAuthenticationHandler", result.getHandlerName()); + } + + @Test(expected = FailedLoginException.class) + public void examineAuthenticateFailure() throws Exception { + alwaysFailHandler.authenticate(new UsernamePasswordCredential("a", "b")); + } + + @Test + public void verifySupports() throws Exception { + assertTrue(alwaysPassHandler.supports(new UsernamePasswordCredential("a", "b"))); + assertTrue(alwaysFailHandler.supports(new UsernamePasswordCredential("a", "b"))); + } + + @Test + public void verifyGetName() throws Exception { + assertEquals("TestAlwaysPassAuthenticationHandler", alwaysPassHandler.getName()); + assertEquals("TestAlwaysFailAuthenticationHandler", alwaysFailHandler.getName()); + } +} diff --git a/cas-server-support-legacy/src/test/java/org/jasig/cas/authentication/handler/TestAlwaysFailAuthenticationHandler.java b/cas-server-support-legacy/src/test/java/org/jasig/cas/authentication/handler/TestAlwaysFailAuthenticationHandler.java new file mode 100644 index 000000000000..340bd44e9bb2 --- /dev/null +++ b/cas-server-support-legacy/src/test/java/org/jasig/cas/authentication/handler/TestAlwaysFailAuthenticationHandler.java @@ -0,0 +1,39 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +import org.jasig.cas.authentication.principal.Credentials; + +/** + * Test authentication handler that always fails. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class TestAlwaysFailAuthenticationHandler implements AuthenticationHandler { + @Override + public boolean authenticate(final Credentials credential) throws AuthenticationException { + return false; + } + + @Override + public boolean supports(final Credentials credential) { + return true; + } +} diff --git a/cas-server-support-legacy/src/test/java/org/jasig/cas/authentication/handler/TestAlwaysPassAuthenticationHandler.java b/cas-server-support-legacy/src/test/java/org/jasig/cas/authentication/handler/TestAlwaysPassAuthenticationHandler.java new file mode 100644 index 000000000000..d6dd18952554 --- /dev/null +++ b/cas-server-support-legacy/src/test/java/org/jasig/cas/authentication/handler/TestAlwaysPassAuthenticationHandler.java @@ -0,0 +1,39 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.authentication.handler; + +import org.jasig.cas.authentication.principal.Credentials; + +/** + * Test authentication handler that always passes. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class TestAlwaysPassAuthenticationHandler implements AuthenticationHandler { + @Override + public boolean authenticate(final Credentials credential) throws AuthenticationException { + return true; + } + + @Override + public boolean supports(final Credentials credential) { + return true; + } +} diff --git a/cas-server-support-legacy/src/test/resources/test-context.xml b/cas-server-support-legacy/src/test/resources/test-context.xml new file mode 100644 index 000000000000..74b1b8fe9063 --- /dev/null +++ b/cas-server-support-legacy/src/test/resources/test-context.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-support-oauth/NOTICE b/cas-server-support-oauth/NOTICE new file mode 100644 index 000000000000..76d71cf218f8 --- /dev/null +++ b/cas-server-support-oauth/NOTICE @@ -0,0 +1,101 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS OAuth Server Support under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + CDI APIs under Apache License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-support-oauth/pom.xml b/cas-server-support-oauth/pom.xml new file mode 100644 index 000000000000..753dd2bb0f43 --- /dev/null +++ b/cas-server-support-oauth/pom.xml @@ -0,0 +1,61 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-support-oauth + jar + Apereo CAS OAuth Server Support + + + + leleuj + Jérôme Leleu + leleuj@gmail.com + -5 + + developer + maintainer + + + + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + com.fasterxml.jackson.core + jackson-databind + + + + + ${project.parent.basedir} + + diff --git a/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/OAuthConstants.java b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/OAuthConstants.java new file mode 100644 index 000000000000..93c97000fb47 --- /dev/null +++ b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/OAuthConstants.java @@ -0,0 +1,102 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.oauth; + +/** + * This class has the main constants for the OAuth implementation. + * + * @author Jerome Leleu + * @since 3.5.0 + */ +public interface OAuthConstants { + + /** The redirect uri. */ + String REDIRECT_URI = "redirect_uri"; + + /** The client id. */ + String CLIENT_ID = "client_id"; + + /** The client secret. */ + String CLIENT_SECRET = "client_secret"; + + /** The approval prompt. */ + String BYPASS_APPROVAL_PROMPT = "bypass_approval_prompt"; + + /** The code. */ + String CODE = "code"; + + /** The service. */ + String SERVICE = "service"; + + /** The ticket. */ + String TICKET = "ticket"; + + /** The state. */ + String STATE = "state"; + + /** The access token. */ + String ACCESS_TOKEN = "access_token"; + + /** The bearer token. */ + String BEARER_TOKEN = "Bearer"; + + /** The OAUT h20_ callbackurl. */ + String OAUTH20_CALLBACKURL = "oauth20_callbackUrl"; + + /** The OAUT h20_ servic e_ name. */ + String OAUTH20_SERVICE_NAME = "oauth20_service_name"; + + /** The OAUT h20_ state. */ + String OAUTH20_STATE = "oauth20_state"; + + /** + * The missing access token. + **/ + String MISSING_ACCESS_TOKEN = "missing_accessToken"; + + /** The expired access token. */ + String EXPIRED_ACCESS_TOKEN = "expired_accessToken"; + + /** The confirm view. */ + String CONFIRM_VIEW = "oauthConfirmView"; + + /** The error view. */ + String ERROR_VIEW = "viewServiceErrorView"; + + /** The invalid request. */ + String INVALID_REQUEST = "invalid_request"; + + /** The invalid grant. */ + String INVALID_GRANT = "invalid_grant"; + + /** The authorize url. */ + String AUTHORIZE_URL = "authorize"; + + /** The callback authorize url. */ + String CALLBACK_AUTHORIZE_URL = "callbackAuthorize"; + + /** The access token url. */ + String ACCESS_TOKEN_URL = "accessToken"; + + /** The profile url. */ + String PROFILE_URL = "profile"; + + /** The remaining time in seconds before expiration with syntax : expires=3600... */ + String EXPIRES = "expires"; +} diff --git a/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/OAuthUtils.java b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/OAuthUtils.java new file mode 100644 index 000000000000..6d32b84ba971 --- /dev/null +++ b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/OAuthUtils.java @@ -0,0 +1,155 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.oauth; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.support.oauth.services.OAuthRegisteredService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.view.RedirectView; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Iterator; + +/** + * This class has some usefull methods to output data in plain text, + * handle redirects, add parameter in url or find the right provider. + * + * @author Jerome Leleu + * @since 3.5.0 + */ +public final class OAuthUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(OAuthUtils.class); + + /** + * Instantiates a new OAuth utils. + */ + private OAuthUtils() { + } + + /** + * Write to the output this error text and return a null view. + * + * @param response http response + * @param error error message + * @param status status code + * @return a null view + */ + public static ModelAndView writeTextError(final HttpServletResponse response, final String error, final int status) { + return OAuthUtils.writeText(response, "error=" + error, status); + } + + /** + * Write to the output the text and return a null view. + * + * @param response http response + * @param text output text + * @param status status code + * @return a null view + */ + public static ModelAndView writeText(final HttpServletResponse response, final String text, final int status) { + try (PrintWriter printWriter = response.getWriter()) { + response.setStatus(status); + printWriter.print(text); + } catch (final IOException e) { + LOGGER.error("Failed to write to response", e); + } + return null; + } + + /** + * Return a view which is a redirection to an url with an error parameter. + * + * @param url redirect url + * @param error error message + * @return A view which is a redirection to an url with an error parameter + */ + public static ModelAndView redirectToError(final String url, final String error) { + String useUrl = url; + if (StringUtils.isBlank(useUrl)) { + useUrl = "/"; + } + return OAuthUtils.redirectTo(OAuthUtils.addParameter(useUrl, "error", error)); + } + + /** + * Return a view which is a redirection to an url. + * + * @param url redirect url + * @return A view which is a redirection to an url + */ + public static ModelAndView redirectTo(final String url) { + return new ModelAndView(new RedirectView(url)); + } + + /** + * Add a parameter with given name and value to an url. + * + * @param url url to which parameters will be added + * @param name name of parameter + * @param value parameter value + * @return the url with the parameter + */ + public static String addParameter(final String url, final String name, final String value) { + final StringBuilder sb = new StringBuilder(); + sb.append(url); + if (url.indexOf('?') >= 0) { + sb.append('&'); + } else { + sb.append('?'); + } + sb.append(name); + sb.append('='); + if (value != null) { + try { + sb.append(URLEncoder.encode(value, "UTF-8")); + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + return sb.toString(); + } + + /** + * Locate the requested instance of {@link OAuthRegisteredService} by the given clientId. + * @param servicesManager the service registry DAO instance. + * @param clientId the client id by which the {@link OAuthRegisteredService} is to be located. + * @return null, or the located {@link OAuthRegisteredService} instance in the service registry. + */ + public static OAuthRegisteredService getRegisteredOAuthService(final ServicesManager servicesManager, final String clientId) { + final Iterator it = servicesManager.getAllServices().iterator(); + while (it.hasNext()) { + final RegisteredService aService = it.next(); + if (aService instanceof OAuthRegisteredService) { + final OAuthRegisteredService service = (OAuthRegisteredService) aService; + if (service.getClientId().equals(clientId)) { + return service; + } + } + } + return null; + } +} diff --git a/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/services/OAuthCallbackAuthorizeService.java b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/services/OAuthCallbackAuthorizeService.java new file mode 100644 index 000000000000..9dce36eb5b77 --- /dev/null +++ b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/services/OAuthCallbackAuthorizeService.java @@ -0,0 +1,49 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.oauth.services; + +import org.jasig.cas.services.RegexRegisteredService; +import org.jasig.cas.support.oauth.OAuthConstants; + +/** + * An extension of the {@link RegexRegisteredService} that attempts to enforce the + * correct url syntax for the OAuth callback authorize url. The url must end with + * {@link OAuthConstants#CALLBACK_AUTHORIZE_URL}. + * @author Misagh Moayyed + * @since 4.0.0 + */ +public final class OAuthCallbackAuthorizeService extends RegexRegisteredService { + + private static final long serialVersionUID = 1365893114273585648L; + + /** + * {@inheritDoc}. + * @throws IllegalArgumentException if the received url does not end with + * {@link OAuthConstants#CALLBACK_AUTHORIZE_URL} + */ + @Override + public void setServiceId(final String id) { + if (!id.endsWith(OAuthConstants.CALLBACK_AUTHORIZE_URL)) { + final String msg = String.format("OAuth callback authorize service id must end with [%s]", + OAuthConstants.CALLBACK_AUTHORIZE_URL); + throw new IllegalArgumentException(msg); + } + super.setServiceId(id); + } +} diff --git a/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/services/OAuthRegisteredCallbackAuthorizeService.java b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/services/OAuthRegisteredCallbackAuthorizeService.java new file mode 100644 index 000000000000..7bc0724a8f25 --- /dev/null +++ b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/services/OAuthRegisteredCallbackAuthorizeService.java @@ -0,0 +1,50 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.oauth.services; + +import org.jasig.cas.services.RegisteredServiceImpl; +import org.jasig.cas.support.oauth.OAuthConstants; + +/** + * Some Javadoc I will add later. + * @author Misagh Moayyed + * @since 4.0.0 + */ +public final class OAuthRegisteredCallbackAuthorizeService extends RegisteredServiceImpl { + + private static final long serialVersionUID = 2993846310010319047L; + + /** + * Sets the callback authorize url. + * + * @param url the new callback authorize url + */ + public void setCallbackAuthorizeUrl(final String url) { + if (!url.endsWith(OAuthConstants.CALLBACK_AUTHORIZE_URL)) { + throw new IllegalArgumentException("Calllback authorize url must end with " + + OAuthConstants.CALLBACK_AUTHORIZE_URL); + } + super.setServiceId(url); + } + + @Override + public void setServiceId(final String id) { + this.setCallbackAuthorizeUrl(id); + } +} diff --git a/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/services/OAuthRegisteredService.java b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/services/OAuthRegisteredService.java new file mode 100644 index 000000000000..a2cefa7c022a --- /dev/null +++ b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/services/OAuthRegisteredService.java @@ -0,0 +1,72 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.oauth.services; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.jasig.cas.services.RegexRegisteredService; + +/** + * An extension of the {@link RegexRegisteredService} that defines the + * OAuth client id and secret for a given registered service. + * @author Misagh Moayyed + * @since 4.0.0 + */ +public final class OAuthRegisteredService extends RegexRegisteredService { + + private static final long serialVersionUID = 5318897374067731021L; + + private String clientSecret; + + private String clientId; + + private Boolean bypassApprovalPrompt = Boolean.FALSE; + + public String getClientId() { + return this.clientId; + } + + public void setClientId(final String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return this.clientSecret; + } + + public void setClientSecret(final String clientSecret) { + this.clientSecret = clientSecret; + } + + public Boolean isBypassApprovalPrompt() { + return bypassApprovalPrompt; + } + + public void setBypassApprovalPrompt(final Boolean bypassApprovalPrompt) { + this.bypassApprovalPrompt = bypassApprovalPrompt; + } + + @Override + public String toString() { + final ToStringBuilder builder = new ToStringBuilder(this); + builder.appendSuper(super.toString()); + builder.append("clientId", getClientId()); + builder.append("approvalPrompt", isBypassApprovalPrompt()); + return builder.toString(); + } +} diff --git a/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/BaseOAuthWrapperController.java b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/BaseOAuthWrapperController.java new file mode 100644 index 000000000000..7df52af9152e --- /dev/null +++ b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/BaseOAuthWrapperController.java @@ -0,0 +1,115 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.oauth.web; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.AbstractController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; + +/** + * This controller is the base controller for wrapping OAuth protocol in CAS. + * It finds the right sub controller to call according to the url. + * + * @author Jerome Leleu + * @since 3.5.0 + */ +public abstract class BaseOAuthWrapperController extends AbstractController { + + /** The logger. */ + protected final Logger logger = LoggerFactory.getLogger(BaseOAuthWrapperController.class); + + /** The login url. */ + @NotNull + protected String loginUrl; + + /** The services manager. */ + @NotNull + protected ServicesManager servicesManager; + + /** The ticket registry. */ + @NotNull + protected TicketRegistry ticketRegistry; + + /** The timeout. */ + @NotNull + protected long timeout; + + @Override + protected ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + + final String method = getMethod(request); + logger.debug("method : {}", method); + return internalHandleRequest(method, request, response); + } + + /** + * Internal handle request. + * + * @param method the method + * @param request the request + * @param response the response + * @return the model and view + * @throws Exception the exception + */ + protected abstract ModelAndView internalHandleRequest(String method, HttpServletRequest request, + HttpServletResponse response) throws Exception; + + /** + * Return the method to call according to the url. + * + * @param request the incoming http request + * @return the method to call according to the url + */ + private String getMethod(final HttpServletRequest request) { + String method = request.getRequestURI(); + if (method.indexOf('?') >= 0) { + method = StringUtils.substringBefore(method, "?"); + } + final int pos = method.lastIndexOf('/'); + if (pos >= 0) { + method = method.substring(pos + 1); + } + return method; + } + + public void setServicesManager(final ServicesManager servicesManager) { + this.servicesManager = servicesManager; + } + + public void setTicketRegistry(final TicketRegistry ticketRegistry) { + this.ticketRegistry = ticketRegistry; + } + + public void setLoginUrl(final String loginUrl) { + this.loginUrl = loginUrl; + } + + public void setTimeout(final long timeout) { + this.timeout = timeout; + } +} diff --git a/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/OAuth20AccessTokenController.java b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/OAuth20AccessTokenController.java new file mode 100644 index 000000000000..45ddec5b8dff --- /dev/null +++ b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/OAuth20AccessTokenController.java @@ -0,0 +1,163 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.oauth.web; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpStatus; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.support.oauth.OAuthConstants; +import org.jasig.cas.support.oauth.OAuthUtils; +import org.jasig.cas.support.oauth.services.OAuthRegisteredService; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.AbstractController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.concurrent.TimeUnit; + +/** + * This controller returns an access token which is the CAS + * granting ticket according to the service and code (service ticket) given. + * + * @author Jerome Leleu + * @since 3.5.0 + */ +public final class OAuth20AccessTokenController extends AbstractController { + + private static final Logger LOGGER = LoggerFactory.getLogger(OAuth20AccessTokenController.class); + + private final ServicesManager servicesManager; + + private final TicketRegistry ticketRegistry; + + private final long timeout; + + /** + * Instantiates a new o auth20 access token controller. + * + * @param servicesManager the services manager + * @param ticketRegistry the ticket registry + * @param timeout the timeout + */ + public OAuth20AccessTokenController(final ServicesManager servicesManager, final TicketRegistry ticketRegistry, + final long timeout) { + this.servicesManager = servicesManager; + this.ticketRegistry = ticketRegistry; + this.timeout = timeout; + } + + @Override + protected ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + + final String redirectUri = request.getParameter(OAuthConstants.REDIRECT_URI); + LOGGER.debug("{} : {}", OAuthConstants.REDIRECT_URI, redirectUri); + + final String clientId = request.getParameter(OAuthConstants.CLIENT_ID); + LOGGER.debug("{} : {}", OAuthConstants.CLIENT_ID, clientId); + + final String clientSecret = request.getParameter(OAuthConstants.CLIENT_SECRET); + + final String code = request.getParameter(OAuthConstants.CODE); + LOGGER.debug("{} : {}", OAuthConstants.CODE, code); + + final boolean isVerified = verifyAccessTokenRequest(response, redirectUri, clientId, clientSecret, code); + if (!isVerified) { + return OAuthUtils.writeTextError(response, OAuthConstants.INVALID_REQUEST, HttpStatus.SC_BAD_REQUEST); + } + + final ServiceTicket serviceTicket = (ServiceTicket) ticketRegistry.getTicket(code); + // service ticket should be valid + if (serviceTicket == null || serviceTicket.isExpired()) { + LOGGER.error("Code expired : {}", code); + return OAuthUtils.writeTextError(response, OAuthConstants.INVALID_GRANT, HttpStatus.SC_BAD_REQUEST); + } + final TicketGrantingTicket ticketGrantingTicket = serviceTicket.getGrantingTicket(); + // remove service ticket + ticketRegistry.deleteTicket(serviceTicket.getId()); + + response.setContentType("text/plain"); + final int expires = (int) (timeout - TimeUnit.MILLISECONDS + .toSeconds(System.currentTimeMillis() - ticketGrantingTicket.getCreationTime())); + + final String text = String.format("%s=%s&%s=%s", OAuthConstants.ACCESS_TOKEN, ticketGrantingTicket.getId(), + OAuthConstants.EXPIRES, expires); + LOGGER.debug("text : {}", text); + return OAuthUtils.writeText(response, text, HttpStatus.SC_OK); + } + + /** + * Verify access token request by reviewing the values of + * client id, redirect uri, client secret, code, etc. + * + * @param response the response + * @param redirectUri the redirect uri + * @param clientId the client id + * @param clientSecret the client secret + * @param code the code + * @return true, if successful + */ + private boolean verifyAccessTokenRequest(final HttpServletResponse response, final String redirectUri, + final String clientId, final String clientSecret, final String code) { + + // clientId is required + if (StringUtils.isBlank(clientId)) { + LOGGER.error("Missing {}", OAuthConstants.CLIENT_ID); + return false; + } + // redirectUri is required + if (StringUtils.isBlank(redirectUri)) { + LOGGER.error("Missing {}", OAuthConstants.REDIRECT_URI); + return false; + } + // clientSecret is required + if (StringUtils.isBlank(clientSecret)) { + LOGGER.error("Missing {}", OAuthConstants.CLIENT_SECRET); + return false; + } + // code is required + if (StringUtils.isBlank(code)) { + LOGGER.error("Missing {}", OAuthConstants.CODE); + return false; + } + + final OAuthRegisteredService service = OAuthUtils.getRegisteredOAuthService(this.servicesManager, clientId); + if (service == null) { + LOGGER.error("Unknown {} : {}", OAuthConstants.CLIENT_ID, clientId); + return false; + } + + final String serviceId = service.getServiceId(); + if (!redirectUri.matches(serviceId)) { + LOGGER.error("Unsupported {} : {} for serviceId : {}", OAuthConstants.REDIRECT_URI, redirectUri, serviceId); + return false; + } + + if (!StringUtils.equals(service.getClientSecret(), clientSecret)) { + LOGGER.error("Wrong client secret for service {}", service); + return false; + } + return true; + } +} diff --git a/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/OAuth20AuthorizeController.java b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/OAuth20AuthorizeController.java new file mode 100644 index 000000000000..65312cd108a4 --- /dev/null +++ b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/OAuth20AuthorizeController.java @@ -0,0 +1,114 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.oauth.web; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.support.oauth.OAuthConstants; +import org.jasig.cas.support.oauth.OAuthUtils; +import org.jasig.cas.support.oauth.services.OAuthRegisteredService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.AbstractController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +/** + * This controller is in charge of responding to the authorize + * call in OAuth protocol. It stores the callback url and redirects user to the + * login page with the callback service. + * + * @author Jerome Leleu + * @since 3.5.0 + */ +public final class OAuth20AuthorizeController extends AbstractController { + + private static final Logger LOGGER = LoggerFactory.getLogger(OAuth20AuthorizeController.class); + + private final String loginUrl; + + private final ServicesManager servicesManager; + + /** + * Instantiates a new o auth20 authorize controller. + * + * @param servicesManager the services manager + * @param loginUrl the login url + */ + public OAuth20AuthorizeController(final ServicesManager servicesManager, final String loginUrl) { + this.servicesManager = servicesManager; + this.loginUrl = loginUrl; + } + + @Override + protected ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + + final String clientId = request.getParameter(OAuthConstants.CLIENT_ID); + LOGGER.debug("{} : {}", OAuthConstants.CLIENT_ID, clientId); + + final String redirectUri = request.getParameter(OAuthConstants.REDIRECT_URI); + LOGGER.debug("{} : {}", OAuthConstants.REDIRECT_URI, redirectUri); + + final String state = request.getParameter(OAuthConstants.STATE); + LOGGER.debug("{} : {}", OAuthConstants.STATE, state); + + // clientId is required + if (StringUtils.isBlank(clientId)) { + LOGGER.error("Missing {}", OAuthConstants.CLIENT_ID); + return new ModelAndView(OAuthConstants.ERROR_VIEW); + } + // redirectUri is required + if (StringUtils.isBlank(redirectUri)) { + LOGGER.error("Missing {}", OAuthConstants.REDIRECT_URI); + return new ModelAndView(OAuthConstants.ERROR_VIEW); + } + + final OAuthRegisteredService service = OAuthUtils.getRegisteredOAuthService(this.servicesManager, clientId); + if (service == null) { + LOGGER.error("Unknown {} : {}", OAuthConstants.CLIENT_ID, clientId); + return new ModelAndView(OAuthConstants.ERROR_VIEW); + } + + final String serviceId = service.getServiceId(); + if (!redirectUri.matches(serviceId)) { + LOGGER.error("Unsupported {} : {} for serviceId : {}", OAuthConstants.REDIRECT_URI, redirectUri, serviceId); + return new ModelAndView(OAuthConstants.ERROR_VIEW); + } + + // keep info in session + final HttpSession session = request.getSession(); + session.setAttribute(OAuthConstants.OAUTH20_CALLBACKURL, redirectUri); + session.setAttribute(OAuthConstants.OAUTH20_SERVICE_NAME, service.getName()); + session.setAttribute(OAuthConstants.BYPASS_APPROVAL_PROMPT, service.isBypassApprovalPrompt()); + session.setAttribute(OAuthConstants.OAUTH20_STATE, state); + + final String callbackAuthorizeUrl = request.getRequestURL().toString() + .replace("/" + OAuthConstants.AUTHORIZE_URL, "/" + OAuthConstants.CALLBACK_AUTHORIZE_URL); + LOGGER.debug("{} : {}", OAuthConstants.CALLBACK_AUTHORIZE_URL, callbackAuthorizeUrl); + + final String loginUrlWithService = OAuthUtils.addParameter(loginUrl, OAuthConstants.SERVICE, + callbackAuthorizeUrl); + LOGGER.debug("loginUrlWithService : {}", loginUrlWithService); + return OAuthUtils.redirectTo(loginUrlWithService); + } +} diff --git a/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/OAuth20CallbackAuthorizeController.java b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/OAuth20CallbackAuthorizeController.java new file mode 100644 index 000000000000..d9bba00bd5e7 --- /dev/null +++ b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/OAuth20CallbackAuthorizeController.java @@ -0,0 +1,96 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.oauth.web; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.support.oauth.OAuthConstants; +import org.jasig.cas.support.oauth.OAuthUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.AbstractController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.util.HashMap; +import java.util.Map; + +/** + * This controller is called after successful authentication and + * redirects user to the callback url of the OAuth application. A code is + * added which is the service ticket retrieved from previous authentication. + * + * @author Jerome Leleu + * @since 3.5.0 + */ +public final class OAuth20CallbackAuthorizeController extends AbstractController { + + private final Logger logger = LoggerFactory.getLogger(OAuth20CallbackAuthorizeController.class); + + @Override + protected ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + // get CAS ticket + final String ticket = request.getParameter(OAuthConstants.TICKET); + logger.debug("{} : {}", OAuthConstants.TICKET, ticket); + + // retrieve callback url from session + final HttpSession session = request.getSession(); + String callbackUrl = (String) session.getAttribute(OAuthConstants.OAUTH20_CALLBACKURL); + logger.debug("{} : {}", OAuthConstants.OAUTH20_CALLBACKURL, callbackUrl); + session.removeAttribute(OAuthConstants.OAUTH20_CALLBACKURL); + + if (StringUtils.isBlank(callbackUrl)) { + logger.error("{} is missing from the session and can not be retrieved.", OAuthConstants.OAUTH20_CALLBACKURL); + return new ModelAndView(OAuthConstants.ERROR_VIEW); + } + // and state + final String state = (String) session.getAttribute(OAuthConstants.OAUTH20_STATE); + logger.debug("{} : {}", OAuthConstants.OAUTH20_STATE, state); + session.removeAttribute(OAuthConstants.OAUTH20_STATE); + + // return callback url with code & state + callbackUrl = OAuthUtils.addParameter(callbackUrl, OAuthConstants.CODE, ticket); + if (state != null) { + callbackUrl = OAuthUtils.addParameter(callbackUrl, OAuthConstants.STATE, state); + } + logger.debug("{} : {}", OAuthConstants.OAUTH20_CALLBACKURL, callbackUrl); + + final Map model = new HashMap<>(); + model.put("callbackUrl", callbackUrl); + + final Boolean bypassApprovalPrompt = (Boolean) session.getAttribute(OAuthConstants.BYPASS_APPROVAL_PROMPT); + logger.debug("bypassApprovalPrompt : {}", bypassApprovalPrompt); + session.removeAttribute(OAuthConstants.BYPASS_APPROVAL_PROMPT); + + // Clients that auto-approve do not need authorization. + if (bypassApprovalPrompt != null && bypassApprovalPrompt) { + return OAuthUtils.redirectTo(callbackUrl); + } + + // retrieve service name from session + final String serviceName = (String) session.getAttribute(OAuthConstants.OAUTH20_SERVICE_NAME); + logger.debug("serviceName : {}", serviceName); + model.put("serviceName", serviceName); + + return new ModelAndView(OAuthConstants.CONFIRM_VIEW, model); + + } +} diff --git a/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/OAuth20ProfileController.java b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/OAuth20ProfileController.java new file mode 100644 index 000000000000..9035709ec9dd --- /dev/null +++ b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/OAuth20ProfileController.java @@ -0,0 +1,115 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.oauth.web; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.support.oauth.OAuthConstants; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.AbstractController; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +/** + * This controller returns a profile for the authenticated user + * (identifier + attributes), found with the access token (CAS granting + * ticket). + * + * @author Jerome Leleu + * @since 3.5.0 + */ +public final class OAuth20ProfileController extends AbstractController { + + private static final Logger LOGGER = LoggerFactory.getLogger(OAuth20ProfileController.class); + + private static final String ID = "id"; + + private static final String ATTRIBUTES = "attributes"; + + private final TicketRegistry ticketRegistry; + + private final JsonFactory jsonFactory = new JsonFactory(new ObjectMapper()); + + /** + * Instantiates a new o auth20 profile controller. + * + * @param ticketRegistry the ticket registry + */ + public OAuth20ProfileController(final TicketRegistry ticketRegistry) { + this.ticketRegistry = ticketRegistry; + } + + @Override + protected ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) throws Exception { + String accessToken = request.getParameter(OAuthConstants.ACCESS_TOKEN); + if (StringUtils.isBlank(accessToken)) { + final String authHeader = request.getHeader("Authorization"); + if (StringUtils.isNotBlank(authHeader) + && authHeader.toLowerCase().startsWith(OAuthConstants.BEARER_TOKEN.toLowerCase() + ' ')) { + accessToken = authHeader.substring(OAuthConstants.BEARER_TOKEN.length() + 1); + } + } + LOGGER.debug("{} : {}", OAuthConstants.ACCESS_TOKEN, accessToken); + + try (final JsonGenerator jsonGenerator = this.jsonFactory.createJsonGenerator(response.getWriter())) { + response.setContentType("application/json"); + // accessToken is required + if (StringUtils.isBlank(accessToken)) { + LOGGER.error("Missing {}", OAuthConstants.ACCESS_TOKEN); + jsonGenerator.writeStartObject(); + jsonGenerator.writeStringField("error", OAuthConstants.MISSING_ACCESS_TOKEN); + jsonGenerator.writeEndObject(); + return null; + } + // get ticket granting ticket + final TicketGrantingTicket ticketGrantingTicket = (TicketGrantingTicket) this.ticketRegistry.getTicket(accessToken); + if (ticketGrantingTicket == null || ticketGrantingTicket.isExpired()) { + LOGGER.error("expired accessToken : {}", accessToken); + jsonGenerator.writeStartObject(); + jsonGenerator.writeStringField("error", OAuthConstants.EXPIRED_ACCESS_TOKEN); + jsonGenerator.writeEndObject(); + return null; + } + // generate profile : identifier + attributes + final Principal principal = ticketGrantingTicket.getAuthentication().getPrincipal(); + jsonGenerator.writeStartObject(); + jsonGenerator.writeStringField(ID, principal.getId()); + jsonGenerator.writeArrayFieldStart(ATTRIBUTES); + final Map attributes = principal.getAttributes(); + for (final Map.Entry entry : attributes.entrySet()) { + jsonGenerator.writeStartObject(); + jsonGenerator.writeObjectField(entry.getKey(), entry.getValue()); + jsonGenerator.writeEndObject(); + } + jsonGenerator.writeEndArray(); + jsonGenerator.writeEndObject(); + return null; + } finally { + response.flushBuffer(); + } + } +} diff --git a/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/OAuth20WrapperController.java b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/OAuth20WrapperController.java new file mode 100644 index 000000000000..7f9eb89d536a --- /dev/null +++ b/cas-server-support-oauth/src/main/java/org/jasig/cas/support/oauth/web/OAuth20WrapperController.java @@ -0,0 +1,83 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.oauth.web; + +import org.apache.http.HttpStatus; +import org.jasig.cas.support.oauth.OAuthConstants; +import org.jasig.cas.support.oauth.OAuthUtils; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.AbstractController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * This controller is the main entry point for OAuth version 2.0 + * wrapping in CAS, should be mapped to something like /oauth2.0/*. Dispatch + * request to specific controllers : authorize, accessToken... + * + * @author Jerome Leleu + * @since 3.5.0 + */ +public final class OAuth20WrapperController extends BaseOAuthWrapperController implements InitializingBean { + + private AbstractController authorizeController; + + private AbstractController callbackAuthorizeController; + + private AbstractController accessTokenController; + + private AbstractController profileController; + + @Override + public void afterPropertiesSet() throws Exception { + authorizeController = new OAuth20AuthorizeController(servicesManager, loginUrl); + callbackAuthorizeController = new OAuth20CallbackAuthorizeController(); + accessTokenController = new OAuth20AccessTokenController(servicesManager, ticketRegistry, timeout); + profileController = new OAuth20ProfileController(ticketRegistry); + } + + @Override + protected ModelAndView internalHandleRequest(final String method, final HttpServletRequest request, + final HttpServletResponse response) throws Exception { + + // authorize + if (OAuthConstants.AUTHORIZE_URL.equals(method)) { + return authorizeController.handleRequest(request, response); + } + // callback on authorize + if (OAuthConstants.CALLBACK_AUTHORIZE_URL.equals(method)) { + return callbackAuthorizeController.handleRequest(request, response); + } + //get access token + if (OAuthConstants.ACCESS_TOKEN_URL.equals(method)) { + return accessTokenController.handleRequest(request, response); + } + // get profile + if (OAuthConstants.PROFILE_URL.equals(method)) { + return profileController.handleRequest(request, response); + } + + // else error + logger.error("Unknown method : {}", method); + OAuthUtils.writeTextError(response, OAuthConstants.INVALID_REQUEST, HttpStatus.SC_OK); + return null; + } +} diff --git a/cas-server-support-oauth/src/site/site.xml b/cas-server-support-oauth/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-support-oauth/src/site/site.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/OAuthTestSuite.java b/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/OAuthTestSuite.java new file mode 100644 index 000000000000..15d0c5f7ffa1 --- /dev/null +++ b/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/OAuthTestSuite.java @@ -0,0 +1,38 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.oauth; + +import org.jasig.cas.support.oauth.web.OAuth20AccessTokenControllerTests; +import org.jasig.cas.support.oauth.web.OAuth20AuthorizeControllerTests; +import org.jasig.cas.support.oauth.web.OAuth20CallbackAuthorizeControllerTests; +import org.jasig.cas.support.oauth.web.OAuth20ProfileControllerTests; +import org.jasig.cas.support.oauth.web.OAuth20WrapperControllerTests; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({OAuth20AccessTokenControllerTests.class, OAuth20AuthorizeControllerTests.class, + OAuth20CallbackAuthorizeControllerTests.class, OAuth20ProfileControllerTests.class, + OAuth20WrapperControllerTests.class}) +/** + * OAuth test suite that runs all test in a batch. + * @author Misagh Moayyed + * @since 4.0.0 + */ +public class OAuthTestSuite {} diff --git a/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/web/OAuth20AccessTokenControllerTests.java b/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/web/OAuth20AccessTokenControllerTests.java new file mode 100644 index 000000000000..1c8ed7e6ba6e --- /dev/null +++ b/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/web/OAuth20AccessTokenControllerTests.java @@ -0,0 +1,289 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.oauth.web; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.support.oauth.OAuthConstants; +import org.jasig.cas.support.oauth.services.OAuthRegisteredService; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +/** + * This class tests the {@link OAuth20AccessTokenController} class. + * + * @author Jerome Leleu + * @since 3.5.2 + */ +public final class OAuth20AccessTokenControllerTests { + + private static final String CONTEXT = "/oauth2.0/"; + + private static final String CLIENT_ID = "1"; + + private static final String CLIENT_SECRET = "secret"; + + private static final String WRONG_CLIENT_SECRET = "wrongSecret"; + + private static final String CODE = "ST-1"; + + private static final String TGT_ID = "TGT-1"; + + private static final String REDIRECT_URI = "http://someurl"; + + private static final String OTHER_REDIRECT_URI = "http://someotherurl"; + + private static final int TIMEOUT = 7200; + + @Test + public void verifyNoClientId() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.ACCESS_TOKEN_URL); + mockRequest.setParameter(OAuthConstants.REDIRECT_URI, REDIRECT_URI); + mockRequest.setParameter(OAuthConstants.CLIENT_SECRET, CLIENT_SECRET); + mockRequest.setParameter(OAuthConstants.CODE, CODE); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.afterPropertiesSet(); + oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(400, mockResponse.getStatus()); + assertEquals("error=" + OAuthConstants.INVALID_REQUEST, mockResponse.getContentAsString()); + } + + @Test + public void verifyNoRedirectUri() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.ACCESS_TOKEN_URL); + mockRequest.setParameter(OAuthConstants.CLIENT_ID, CLIENT_ID); + mockRequest.setParameter(OAuthConstants.CLIENT_SECRET, CLIENT_SECRET); + mockRequest.setParameter(OAuthConstants.CODE, CODE); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.afterPropertiesSet(); + oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(400, mockResponse.getStatus()); + assertEquals("error=" + OAuthConstants.INVALID_REQUEST, mockResponse.getContentAsString()); + } + + @Test + public void verifyNoClientSecret() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.ACCESS_TOKEN_URL); + mockRequest.setParameter(OAuthConstants.CLIENT_ID, CLIENT_ID); + mockRequest.setParameter(OAuthConstants.REDIRECT_URI, REDIRECT_URI); + mockRequest.setParameter(OAuthConstants.CODE, CODE); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.afterPropertiesSet(); + oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(400, mockResponse.getStatus()); + assertEquals("error=" + OAuthConstants.INVALID_REQUEST, mockResponse.getContentAsString()); + } + + @Test + public void verifyNoCode() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.ACCESS_TOKEN_URL); + mockRequest.setParameter(OAuthConstants.CLIENT_ID, CLIENT_ID); + mockRequest.setParameter(OAuthConstants.REDIRECT_URI, REDIRECT_URI); + mockRequest.setParameter(OAuthConstants.CLIENT_SECRET, CLIENT_SECRET); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.afterPropertiesSet(); + oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(400, mockResponse.getStatus()); + assertEquals("error=" + OAuthConstants.INVALID_REQUEST, mockResponse.getContentAsString()); + } + + @Test + public void verifyNoCasService() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.ACCESS_TOKEN_URL); + mockRequest.setParameter(OAuthConstants.CLIENT_ID, CLIENT_ID); + mockRequest.setParameter(OAuthConstants.REDIRECT_URI, REDIRECT_URI); + mockRequest.setParameter(OAuthConstants.CLIENT_SECRET, CLIENT_SECRET); + mockRequest.setParameter(OAuthConstants.CODE, CODE); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final ServicesManager servicesManager = mock(ServicesManager.class); + when(servicesManager.getAllServices()).thenReturn(new ArrayList()); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.setServicesManager(servicesManager); + oauth20WrapperController.afterPropertiesSet(); + oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(400, mockResponse.getStatus()); + assertEquals("error=" + OAuthConstants.INVALID_REQUEST, mockResponse.getContentAsString()); + } + + @Test + public void verifyRedirectUriDoesNotStartWithServiceId() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.ACCESS_TOKEN_URL); + mockRequest.setParameter(OAuthConstants.CLIENT_ID, CLIENT_ID); + mockRequest.setParameter(OAuthConstants.REDIRECT_URI, REDIRECT_URI); + mockRequest.setParameter(OAuthConstants.CLIENT_SECRET, CLIENT_SECRET); + mockRequest.setParameter(OAuthConstants.CODE, CODE); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final ServicesManager servicesManager = mock(ServicesManager.class); + final List services = new ArrayList<>(); + services.add(getRegisteredService(OTHER_REDIRECT_URI, CLIENT_SECRET)); + when(servicesManager.getAllServices()).thenReturn(services); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.setServicesManager(servicesManager); + oauth20WrapperController.afterPropertiesSet(); + oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(400, mockResponse.getStatus()); + assertEquals("error=" + OAuthConstants.INVALID_REQUEST, mockResponse.getContentAsString()); + } + + @Test + public void verifyWrongSecret() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.ACCESS_TOKEN_URL); + mockRequest.setParameter(OAuthConstants.CLIENT_ID, CLIENT_ID); + mockRequest.setParameter(OAuthConstants.REDIRECT_URI, REDIRECT_URI); + mockRequest.setParameter(OAuthConstants.CLIENT_SECRET, CLIENT_SECRET); + mockRequest.setParameter(OAuthConstants.CODE, CODE); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final ServicesManager servicesManager = mock(ServicesManager.class); + final List services = new ArrayList<>(); + services.add(getRegisteredService(REDIRECT_URI, WRONG_CLIENT_SECRET)); + when(servicesManager.getAllServices()).thenReturn(services); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.setServicesManager(servicesManager); + oauth20WrapperController.afterPropertiesSet(); + oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(400, mockResponse.getStatus()); + assertEquals("error=" + OAuthConstants.INVALID_REQUEST, mockResponse.getContentAsString()); + } + + @Test + public void verifyNoServiceTicket() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.ACCESS_TOKEN_URL); + mockRequest.setParameter(OAuthConstants.CLIENT_ID, CLIENT_ID); + mockRequest.setParameter(OAuthConstants.REDIRECT_URI, REDIRECT_URI); + mockRequest.setParameter(OAuthConstants.CLIENT_SECRET, CLIENT_SECRET); + mockRequest.setParameter(OAuthConstants.CODE, CODE); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final ServicesManager servicesManager = mock(ServicesManager.class); + final List services = new ArrayList<>(); + services.add(getRegisteredService(REDIRECT_URI, CLIENT_SECRET)); + when(servicesManager.getAllServices()).thenReturn(services); + final TicketRegistry ticketRegistry = mock(TicketRegistry.class); + when(ticketRegistry.getTicket(CODE)).thenReturn(null); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.setServicesManager(servicesManager); + oauth20WrapperController.setTicketRegistry(ticketRegistry); + oauth20WrapperController.afterPropertiesSet(); + oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(400, mockResponse.getStatus()); + assertEquals("error=" + OAuthConstants.INVALID_GRANT, mockResponse.getContentAsString()); + } + + @Test + public void verifyExpiredServiceTicket() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.ACCESS_TOKEN_URL); + mockRequest.setParameter(OAuthConstants.CLIENT_ID, CLIENT_ID); + mockRequest.setParameter(OAuthConstants.REDIRECT_URI, REDIRECT_URI); + mockRequest.setParameter(OAuthConstants.CLIENT_SECRET, CLIENT_SECRET); + mockRequest.setParameter(OAuthConstants.CODE, CODE); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final ServicesManager servicesManager = mock(ServicesManager.class); + final List services = new ArrayList<>(); + services.add(getRegisteredService(REDIRECT_URI, CLIENT_SECRET)); + when(servicesManager.getAllServices()).thenReturn(services); + final TicketRegistry ticketRegistry = mock(TicketRegistry.class); + final ServiceTicket serviceTicket = mock(ServiceTicket.class); + when(serviceTicket.isExpired()).thenReturn(true); + when(ticketRegistry.getTicket(CODE)).thenReturn(serviceTicket); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.setServicesManager(servicesManager); + oauth20WrapperController.setTicketRegistry(ticketRegistry); + oauth20WrapperController.afterPropertiesSet(); + oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(400, mockResponse.getStatus()); + assertEquals("error=" + OAuthConstants.INVALID_GRANT, mockResponse.getContentAsString()); + } + + @Test + public void verifyOK() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.ACCESS_TOKEN_URL); + mockRequest.setParameter(OAuthConstants.CLIENT_ID, CLIENT_ID); + mockRequest.setParameter(OAuthConstants.REDIRECT_URI, REDIRECT_URI); + mockRequest.setParameter(OAuthConstants.CLIENT_SECRET, CLIENT_SECRET); + mockRequest.setParameter(OAuthConstants.CODE, CODE); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final ServicesManager servicesManager = mock(ServicesManager.class); + final List services = new ArrayList<>(); + services.add(getRegisteredService(REDIRECT_URI, CLIENT_SECRET)); + when(servicesManager.getAllServices()).thenReturn(services); + final TicketRegistry ticketRegistry = mock(TicketRegistry.class); + final ServiceTicket serviceTicket = mock(ServiceTicket.class); + final TicketGrantingTicket ticketGrantingTicket = mock(TicketGrantingTicket.class); + // 10 seconds + final int timeBefore = 10; + when(ticketGrantingTicket.getCreationTime()).thenReturn(System.currentTimeMillis() - timeBefore * 1000); + when(ticketGrantingTicket.getId()).thenReturn(TGT_ID); + when(serviceTicket.isExpired()).thenReturn(false); + when(serviceTicket.getId()).thenReturn(CODE); + when(serviceTicket.getGrantingTicket()).thenReturn(ticketGrantingTicket); + when(ticketRegistry.getTicket(CODE)).thenReturn(serviceTicket); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.setServicesManager(servicesManager); + oauth20WrapperController.setTicketRegistry(ticketRegistry); + oauth20WrapperController.setTimeout(TIMEOUT); + oauth20WrapperController.afterPropertiesSet(); + oauth20WrapperController.handleRequest(mockRequest, mockResponse); + verify(ticketRegistry).deleteTicket(CODE); + assertEquals("text/plain", mockResponse.getContentType()); + assertEquals(200, mockResponse.getStatus()); + final String body = mockResponse.getContentAsString(); + assertTrue(body.startsWith(OAuthConstants.ACCESS_TOKEN + "=" + TGT_ID + "&" + OAuthConstants.EXPIRES + "=")); + // delta = 2 seconds + final int delta = 2; + final int timeLeft = Integer.parseInt(StringUtils.substringAfter(body, "&" + OAuthConstants.EXPIRES + "=")); + assertTrue(timeLeft >= TIMEOUT - timeBefore - delta); + assertTrue(timeLeft <= TIMEOUT - timeBefore + delta); + } + + private RegisteredService getRegisteredService(final String serviceId, final String secret) { + final OAuthRegisteredService registeredServiceImpl = new OAuthRegisteredService(); + registeredServiceImpl.setName("The registered service name"); + registeredServiceImpl.setServiceId(serviceId); + registeredServiceImpl.setClientId(CLIENT_ID); + registeredServiceImpl.setClientSecret(secret); + return registeredServiceImpl; + } +} diff --git a/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/web/OAuth20AuthorizeControllerTests.java b/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/web/OAuth20AuthorizeControllerTests.java new file mode 100644 index 000000000000..d02d501a09a1 --- /dev/null +++ b/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/web/OAuth20AuthorizeControllerTests.java @@ -0,0 +1,211 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.oauth.web; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpSession; + +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.support.oauth.OAuthConstants; +import org.jasig.cas.support.oauth.OAuthUtils; +import org.jasig.cas.support.oauth.services.OAuthRegisteredService; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.View; +import org.springframework.web.servlet.view.RedirectView; + +/** + * This class tests the {@link OAuth20AuthorizeController} class. + * + * @author Jerome Leleu + * @since 3.5.2 + */ +public final class OAuth20AuthorizeControllerTests { + + private static final String CONTEXT = "/oauth2.0/"; + + private static final String CLIENT_ID = "1"; + + private static final String REDIRECT_URI = "http://someurl"; + + private static final String OTHER_REDIRECT_URI = "http://someotherurl"; + + private static final String CAS_SERVER = "casserver"; + + private static final String CAS_SCHEME = "https"; + + private static final int CAS_PORT = 443; + + private static final String CAS_URL = CAS_SCHEME + "://" + CAS_SERVER + ":" + CAS_PORT; + + private static final String SERVICE_NAME = "serviceName"; + + private static final String STATE = "state"; + + @Test + public void verifyNoClientId() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.AUTHORIZE_URL); + mockRequest.setParameter(OAuthConstants.REDIRECT_URI, REDIRECT_URI); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.afterPropertiesSet(); + final ModelAndView modelAndView = oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(OAuthConstants.ERROR_VIEW, modelAndView.getViewName()); + } + + @Test + public void verifyNoRedirectUri() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.AUTHORIZE_URL); + mockRequest.setParameter(OAuthConstants.CLIENT_ID, CLIENT_ID); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.afterPropertiesSet(); + final ModelAndView modelAndView = oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(OAuthConstants.ERROR_VIEW, modelAndView.getViewName()); + } + + @Test + public void verifyNoCasService() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.AUTHORIZE_URL); + mockRequest.setParameter(OAuthConstants.CLIENT_ID, CLIENT_ID); + mockRequest.setParameter(OAuthConstants.REDIRECT_URI, REDIRECT_URI); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final ServicesManager servicesManager = mock(ServicesManager.class); + when(servicesManager.getAllServices()).thenReturn(new ArrayList()); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.setServicesManager(servicesManager); + oauth20WrapperController.afterPropertiesSet(); + final ModelAndView modelAndView = oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(OAuthConstants.ERROR_VIEW, modelAndView.getViewName()); + } + + @Test + public void verifyRedirectUriDoesNotStartWithServiceId() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.AUTHORIZE_URL); + mockRequest.setParameter(OAuthConstants.CLIENT_ID, CLIENT_ID); + mockRequest.setParameter(OAuthConstants.REDIRECT_URI, REDIRECT_URI); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final ServicesManager servicesManager = mock(ServicesManager.class); + final List services = new ArrayList<>(); + services.add(getRegisteredService(OTHER_REDIRECT_URI, CLIENT_ID)); + when(servicesManager.getAllServices()).thenReturn(services); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.setServicesManager(servicesManager); + oauth20WrapperController.afterPropertiesSet(); + final ModelAndView modelAndView = oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(OAuthConstants.ERROR_VIEW, modelAndView.getViewName()); + } + + @Test + public void verifyOK() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.AUTHORIZE_URL); + mockRequest.setParameter(OAuthConstants.CLIENT_ID, CLIENT_ID); + mockRequest.setParameter(OAuthConstants.REDIRECT_URI, REDIRECT_URI); + mockRequest.setServerName(CAS_SERVER); + mockRequest.setServerPort(CAS_PORT); + mockRequest.setScheme(CAS_SCHEME); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final ServicesManager servicesManager = mock(ServicesManager.class); + final List services = new ArrayList<>(); + services.add(getRegisteredService(REDIRECT_URI, SERVICE_NAME)); + when(servicesManager.getAllServices()).thenReturn(services); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.setLoginUrl(CAS_URL); + oauth20WrapperController.setServicesManager(servicesManager); + oauth20WrapperController.afterPropertiesSet(); + final ModelAndView modelAndView = oauth20WrapperController.handleRequest(mockRequest, mockResponse); + final HttpSession session = mockRequest.getSession(); + assertEquals(REDIRECT_URI, session.getAttribute(OAuthConstants.OAUTH20_CALLBACKURL)); + assertEquals(SERVICE_NAME, session.getAttribute(OAuthConstants.OAUTH20_SERVICE_NAME)); + final View view = modelAndView.getView(); + assertTrue(view instanceof RedirectView); + final RedirectView redirectView = (RedirectView) view; + + final MockHttpServletRequest reqSvc = new MockHttpServletRequest("GET", CONTEXT + OAuthConstants.CALLBACK_AUTHORIZE_URL); + reqSvc.setServerName(CAS_SERVER); + reqSvc.setServerPort(CAS_PORT); + reqSvc.setScheme(CAS_SCHEME); + final URL url = new URL(OAuthUtils.addParameter(CAS_URL, "service", reqSvc.getRequestURL().toString())); + final URL url2 = new URL(redirectView.getUrl()); + + assertEquals(url, url2); + } + + @Test + public void verifyOKWithState() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.AUTHORIZE_URL); + mockRequest.setParameter(OAuthConstants.CLIENT_ID, CLIENT_ID); + mockRequest.setParameter(OAuthConstants.REDIRECT_URI, REDIRECT_URI); + mockRequest.setParameter(OAuthConstants.STATE, STATE); + mockRequest.setServerName(CAS_SERVER); + mockRequest.setServerPort(CAS_PORT); + mockRequest.setScheme(CAS_SCHEME); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final ServicesManager servicesManager = mock(ServicesManager.class); + final List services = new ArrayList<>(); + services.add(getRegisteredService(REDIRECT_URI, SERVICE_NAME)); + when(servicesManager.getAllServices()).thenReturn(services); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.setLoginUrl(CAS_URL); + oauth20WrapperController.setServicesManager(servicesManager); + oauth20WrapperController.afterPropertiesSet(); + final ModelAndView modelAndView = oauth20WrapperController.handleRequest(mockRequest, mockResponse); + final HttpSession session = mockRequest.getSession(); + assertEquals(REDIRECT_URI, session.getAttribute(OAuthConstants.OAUTH20_CALLBACKURL)); + assertEquals(SERVICE_NAME, session.getAttribute(OAuthConstants.OAUTH20_SERVICE_NAME)); + assertEquals(STATE, session.getAttribute(OAuthConstants.OAUTH20_STATE)); + final View view = modelAndView.getView(); + assertTrue(view instanceof RedirectView); + final RedirectView redirectView = (RedirectView) view; + + final MockHttpServletRequest reqSvc = new MockHttpServletRequest("GET", CONTEXT + OAuthConstants.CALLBACK_AUTHORIZE_URL); + reqSvc.setServerName(CAS_SERVER); + reqSvc.setServerPort(CAS_PORT); + reqSvc.setScheme(CAS_SCHEME); + final URL url = new URL(OAuthUtils.addParameter(CAS_URL, "service", reqSvc.getRequestURL().toString())); + final URL url2 = new URL(redirectView.getUrl()); + + assertEquals(url, url2); + } + + private RegisteredService getRegisteredService(final String serviceId, final String name) { + final OAuthRegisteredService registeredServiceImpl = new OAuthRegisteredService(); + registeredServiceImpl.setName(name); + registeredServiceImpl.setServiceId(serviceId); + registeredServiceImpl.setClientId(CLIENT_ID); + return registeredServiceImpl; + } +} diff --git a/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/web/OAuth20CallbackAuthorizeControllerTests.java b/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/web/OAuth20CallbackAuthorizeControllerTests.java new file mode 100644 index 000000000000..cda788b1ce51 --- /dev/null +++ b/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/web/OAuth20CallbackAuthorizeControllerTests.java @@ -0,0 +1,93 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.oauth.web; + +import static org.junit.Assert.assertEquals; + +import java.util.Map; + +import org.jasig.cas.support.oauth.OAuthConstants; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.web.servlet.ModelAndView; + +/** + * This class tests the {@link OAuth20CallbackAuthorizeController} class. + * + * @author Jerome Leleu + * @since 3.5.2 + */ +public final class OAuth20CallbackAuthorizeControllerTests { + + private static final String CONTEXT = "/oauth2.0/"; + + private static final String SERVICE_TICKET = "ST-1"; + + private static final String REDIRECT_URI = "http://someurl"; + + private static final String SERVICE_NAME = "serviceName"; + + private static final String STATE = "state"; + + @Test + public void verifyOK() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest( + "GET", + CONTEXT + + OAuthConstants.CALLBACK_AUTHORIZE_URL); + mockRequest.addParameter(OAuthConstants.TICKET, SERVICE_TICKET); + final MockHttpSession mockSession = new MockHttpSession(); + mockSession.putValue(OAuthConstants.OAUTH20_CALLBACKURL, REDIRECT_URI); + mockSession.putValue(OAuthConstants.OAUTH20_SERVICE_NAME, SERVICE_NAME); + mockRequest.setSession(mockSession); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.afterPropertiesSet(); + final ModelAndView modelAndView = oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(OAuthConstants.CONFIRM_VIEW, modelAndView.getViewName()); + final Map map = modelAndView.getModel(); + assertEquals(SERVICE_NAME, map.get("serviceName")); + assertEquals(REDIRECT_URI + "?" + OAuthConstants.CODE + "=" + SERVICE_TICKET, map.get("callbackUrl")); + } + + @Test + public void verifyOKWithState() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest( + "GET", + CONTEXT + + OAuthConstants.CALLBACK_AUTHORIZE_URL); + mockRequest.addParameter(OAuthConstants.TICKET, SERVICE_TICKET); + final MockHttpSession mockSession = new MockHttpSession(); + mockSession.putValue(OAuthConstants.OAUTH20_CALLBACKURL, REDIRECT_URI); + mockSession.putValue(OAuthConstants.OAUTH20_SERVICE_NAME, SERVICE_NAME); + mockSession.putValue(OAuthConstants.OAUTH20_STATE, STATE); + mockRequest.setSession(mockSession); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.afterPropertiesSet(); + final ModelAndView modelAndView = oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(OAuthConstants.CONFIRM_VIEW, modelAndView.getViewName()); + final Map map = modelAndView.getModel(); + assertEquals(SERVICE_NAME, map.get("serviceName")); + assertEquals(REDIRECT_URI + "?" + OAuthConstants.CODE + "=" + SERVICE_TICKET + "&" + OAuthConstants.STATE + "=" + + STATE, map.get("callbackUrl")); + } +} diff --git a/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/web/OAuth20ProfileControllerTests.java b/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/web/OAuth20ProfileControllerTests.java new file mode 100644 index 000000000000..103b62865e9b --- /dev/null +++ b/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/web/OAuth20ProfileControllerTests.java @@ -0,0 +1,195 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.oauth.web; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.support.oauth.OAuthConstants; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class tests the {@link OAuth20ProfileController} class. + * + * @author Jerome Leleu + * @since 3.5.2 + */ +public final class OAuth20ProfileControllerTests { + + private static final String CONTEXT = "/oauth2.0/"; + + private static final String ID = "1234"; + + private static final String TGT_ID = "TGT-1"; + + private static final String NAME = "attributeName"; + + private static final String NAME2 = "attributeName2"; + + private static final String VALUE = "attributeValue"; + + private static final String CONTENT_TYPE = "application/json"; + + @Test + public void verifyNoAccessToken() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.PROFILE_URL); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.afterPropertiesSet(); + oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(200, mockResponse.getStatus()); + assertEquals(CONTENT_TYPE, mockResponse.getContentType()); + assertEquals("{\"error\":\"" + OAuthConstants.MISSING_ACCESS_TOKEN + "\"}", mockResponse.getContentAsString()); + } + + @Test + public void verifyNoTicketGrantingTicketImpl() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.PROFILE_URL); + mockRequest.setParameter(OAuthConstants.ACCESS_TOKEN, TGT_ID); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + final TicketRegistry ticketRegistry = mock(TicketRegistry.class); + when(ticketRegistry.getTicket(TGT_ID)).thenReturn(null); + oauth20WrapperController.setTicketRegistry(ticketRegistry); + oauth20WrapperController.afterPropertiesSet(); + oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(200, mockResponse.getStatus()); + assertEquals(CONTENT_TYPE, mockResponse.getContentType()); + assertEquals("{\"error\":\"" + OAuthConstants.EXPIRED_ACCESS_TOKEN + "\"}", mockResponse.getContentAsString()); + } + + @Test + public void verifyExpiredTicketGrantingTicketImpl() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.PROFILE_URL); + mockRequest.setParameter(OAuthConstants.ACCESS_TOKEN, TGT_ID); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + final TicketRegistry ticketRegistry = mock(TicketRegistry.class); + final TicketGrantingTicket ticketGrantingTicket = mock(TicketGrantingTicket.class); + when(ticketGrantingTicket.isExpired()).thenReturn(true); + when(ticketRegistry.getTicket(TGT_ID)).thenReturn(ticketGrantingTicket); + oauth20WrapperController.setTicketRegistry(ticketRegistry); + oauth20WrapperController.afterPropertiesSet(); + oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(200, mockResponse.getStatus()); + assertEquals(CONTENT_TYPE, mockResponse.getContentType()); + assertEquals("{\"error\":\"" + OAuthConstants.EXPIRED_ACCESS_TOKEN + "\"}", mockResponse.getContentAsString()); + } + + @Test + public void verifyOK() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.PROFILE_URL); + mockRequest.setParameter(OAuthConstants.ACCESS_TOKEN, TGT_ID); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + final TicketRegistry ticketRegistry = mock(TicketRegistry.class); + final TicketGrantingTicket ticketGrantingTicket = mock(TicketGrantingTicket.class); + when(ticketGrantingTicket.isExpired()).thenReturn(false); + when(ticketRegistry.getTicket(TGT_ID)).thenReturn(ticketGrantingTicket); + final Authentication authentication = mock(Authentication.class); + final Principal principal = mock(Principal.class); + when(principal.getId()).thenReturn(ID); + final Map map = new HashMap<>(); + map.put(NAME, VALUE); + final List list = Arrays.asList(VALUE, VALUE); + map.put(NAME2, list); + when(principal.getAttributes()).thenReturn(map); + when(authentication.getPrincipal()).thenReturn(principal); + when(ticketGrantingTicket.getAuthentication()).thenReturn(authentication); + oauth20WrapperController.setTicketRegistry(ticketRegistry); + oauth20WrapperController.afterPropertiesSet(); + oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(200, mockResponse.getStatus()); + assertEquals(CONTENT_TYPE, mockResponse.getContentType()); + + final ObjectMapper mapper = new ObjectMapper(); + + final String expected = "{\"id\":\"" + ID + "\",\"attributes\":[{\"" + NAME + "\":\"" + VALUE + "\"},{\"" + NAME2 + + "\":[\"" + VALUE + "\",\"" + VALUE + "\"]}]}"; + final JsonNode expectedObj = mapper.readTree(expected); + final JsonNode receivedObj = mapper.readTree(mockResponse.getContentAsString()); + assertEquals(expectedObj.get("id").asText(), receivedObj.get("id").asText()); + + final JsonNode expectedAttributes = expectedObj.get("attributes"); + final JsonNode receivedAttributes = receivedObj.get("attributes"); + + assertEquals(expectedAttributes.findValue(NAME).asText(), receivedAttributes.findValue(NAME).asText()); + assertEquals(expectedAttributes.findValues(NAME2), receivedAttributes.findValues(NAME2)); + } + + @Test + public void verifyOKWithAuthorizationHeader() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + + OAuthConstants.PROFILE_URL); + mockRequest.addHeader("Authorization", OAuthConstants.BEARER_TOKEN + " " + TGT_ID); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + final TicketRegistry ticketRegistry = mock(TicketRegistry.class); + final TicketGrantingTicket ticketGrantingTicket = mock(TicketGrantingTicket.class); + when(ticketGrantingTicket.isExpired()).thenReturn(false); + when(ticketRegistry.getTicket(TGT_ID)).thenReturn(ticketGrantingTicket); + final Authentication authentication = mock(Authentication.class); + final Principal principal = mock(Principal.class); + when(principal.getId()).thenReturn(ID); + final Map map = new HashMap<>(); + map.put(NAME, VALUE); + final List list = Arrays.asList(VALUE, VALUE); + map.put(NAME2, list); + when(principal.getAttributes()).thenReturn(map); + when(authentication.getPrincipal()).thenReturn(principal); + when(ticketGrantingTicket.getAuthentication()).thenReturn(authentication); + oauth20WrapperController.setTicketRegistry(ticketRegistry); + oauth20WrapperController.afterPropertiesSet(); + oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(200, mockResponse.getStatus()); + assertEquals(CONTENT_TYPE, mockResponse.getContentType()); + + final ObjectMapper mapper = new ObjectMapper(); + final String expected = "{\"id\":\"" + ID + "\",\"attributes\":[{\"" + NAME + "\":\"" + VALUE + "\"},{\"" + NAME2 + + "\":[\"" + VALUE + "\",\"" + VALUE + "\"]}]}"; + final JsonNode expectedObj = mapper.readTree(expected); + final JsonNode receivedObj = mapper.readTree(mockResponse.getContentAsString()); + + assertEquals(expectedObj.get("id").asText(), receivedObj.get("id").asText()); + + final JsonNode expectedAttributes = expectedObj.get("attributes"); + final JsonNode receivedAttributes = receivedObj.get("attributes"); + + assertEquals(expectedAttributes.findValue(NAME).asText(), receivedAttributes.findValue(NAME).asText()); + assertEquals(expectedAttributes.findValues(NAME2), receivedAttributes.findValues(NAME2)); + } +} diff --git a/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/web/OAuth20WrapperControllerTests.java b/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/web/OAuth20WrapperControllerTests.java new file mode 100644 index 000000000000..9bb169adf2b1 --- /dev/null +++ b/cas-server-support-oauth/src/test/java/org/jasig/cas/support/oauth/web/OAuth20WrapperControllerTests.java @@ -0,0 +1,47 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.oauth.web; + +import static org.junit.Assert.assertEquals; + +import org.jasig.cas.support.oauth.OAuthConstants; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +/** + * This class tests the {@link OAuth20WrapperController} class. + * + * @author Jerome Leleu + * @since 3.5.2 + */ +public class OAuth20WrapperControllerTests { + + private static final String CONTEXT = "/oauth2.0/"; + + @Test + public void verifyWrongMethod() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", CONTEXT + "wrongmethod"); + final MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + final OAuth20WrapperController oauth20WrapperController = new OAuth20WrapperController(); + oauth20WrapperController.handleRequest(mockRequest, mockResponse); + assertEquals(200, mockResponse.getStatus()); + assertEquals("error=" + OAuthConstants.INVALID_REQUEST, mockResponse.getContentAsString()); + } +} diff --git a/cas-server-support-oauth/src/test/resources/log4j2.xml b/cas-server-support-oauth/src/test/resources/log4j2.xml new file mode 100644 index 000000000000..0d9099c9b8cb --- /dev/null +++ b/cas-server-support-oauth/src/test/resources/log4j2.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-support-openid/NOTICE b/cas-server-support-openid/NOTICE new file mode 100644 index 000000000000..439ca2204e16 --- /dev/null +++ b/cas-server-support-openid/NOTICE @@ -0,0 +1,105 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS OpenId Server Support under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + CDI APIs under Apache License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + guice under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Neko HTML under The Apache Software License, Version 2.0 + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + OpenID4Java under Apache 2 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + Xerces2 Java Parser under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-support-openid/pom.xml b/cas-server-support-openid/pom.xml new file mode 100644 index 000000000000..7382769256b0 --- /dev/null +++ b/cas-server-support-openid/pom.xml @@ -0,0 +1,69 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-support-openid + jar + Apereo CAS OpenId Server Support + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + org.springframework.webflow + spring-webflow + compile + + + + org.openid4java + openid4java + ${openid4java.version} + compile + + + + org.apache.httpcomponents + httpclient + + + + org.jasig.cas + cas-server-core + ${project.version} + test-jar + test + + + + + ${project.parent.basedir} + + diff --git a/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/authentication/handler/support/OpenIdCredentialsAuthenticationHandler.java b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/authentication/handler/support/OpenIdCredentialsAuthenticationHandler.java new file mode 100644 index 000000000000..d92ed02483d4 --- /dev/null +++ b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/authentication/handler/support/OpenIdCredentialsAuthenticationHandler.java @@ -0,0 +1,73 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.authentication.handler.support; + +import java.security.GeneralSecurityException; + +import org.jasig.cas.authentication.AbstractAuthenticationHandler; +import org.jasig.cas.authentication.BasicCredentialMetaData; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.support.openid.authentication.principal.OpenIdCredential; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.registry.TicketRegistry; + +import javax.security.auth.login.FailedLoginException; +import javax.validation.constraints.NotNull; + +/** + * Ensures that the OpenId provided matches with the existing + * TicketGrantingTicket. Otherwise, fail authentication. + * + * @author Scott Battaglia + * @since 3.1 + */ +public final class OpenIdCredentialsAuthenticationHandler extends AbstractAuthenticationHandler { + + @NotNull + private TicketRegistry ticketRegistry; + + @Override + public HandlerResult authenticate(final Credential credential) throws GeneralSecurityException { + final OpenIdCredential c = (OpenIdCredential) credential; + + final TicketGrantingTicket t = this.ticketRegistry.getTicket(c.getTicketGrantingTicketId(), + TicketGrantingTicket.class); + + if (t == null || t.isExpired()) { + throw new FailedLoginException("TGT is null or expired."); + } + final Principal principal = t.getAuthentication().getPrincipal(); + if (!principal.getId().equals(c.getUsername())) { + throw new FailedLoginException("Principal ID mismatch"); + } + return new DefaultHandlerResult(this, new BasicCredentialMetaData(c), principal); + } + + @Override + public boolean supports(final Credential credential) { + return credential instanceof OpenIdCredential; + } + + public void setTicketRegistry(final TicketRegistry ticketRegistry) { + this.ticketRegistry = ticketRegistry; + } +} diff --git a/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/authentication/principal/OpenIdCredential.java b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/authentication/principal/OpenIdCredential.java new file mode 100644 index 000000000000..90de44d50d86 --- /dev/null +++ b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/authentication/principal/OpenIdCredential.java @@ -0,0 +1,64 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.authentication.principal; + +import org.jasig.cas.authentication.Credential; + +/** + * @author Scott Battaglia + * @since 3.1 + */ +public final class OpenIdCredential implements Credential { + + private static final long serialVersionUID = -6535869729412406133L; + + private final String ticketGrantingTicketId; + + private final String username; + + /** + * Instantiates a new OpenID credential. + * + * @param ticketGrantingTicketId the ticket granting ticket id + * @param username the username + */ + public OpenIdCredential(final String ticketGrantingTicketId, final String username) { + this.ticketGrantingTicketId = ticketGrantingTicketId; + this.username = username; + } + + public String getTicketGrantingTicketId() { + return this.ticketGrantingTicketId; + } + + public String getUsername() { + return this.username; + } + + @Override + public String getId() { + return this.username; + } + + @Override + public String toString() { + return this.username; + } + +} diff --git a/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/authentication/principal/OpenIdPrincipalResolver.java b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/authentication/principal/OpenIdPrincipalResolver.java new file mode 100644 index 000000000000..2407365f87ce --- /dev/null +++ b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/authentication/principal/OpenIdPrincipalResolver.java @@ -0,0 +1,43 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.authentication.principal; + +import org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver; +import org.jasig.cas.authentication.Credential; + +/** + * Implementation of PrincipalResolver that converts the OpenId + * user name to a Principal. + * + * @author Scott Battaglia + * @since 3.1 + */ +public final class OpenIdPrincipalResolver extends PersonDirectoryPrincipalResolver { + + @Override + protected String extractPrincipalId(final Credential credential) { + return ((OpenIdCredential) credential).getUsername(); + } + + @Override + public boolean supports(final Credential credential) { + return credential instanceof OpenIdCredential; + } + +} diff --git a/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/authentication/principal/OpenIdService.java b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/authentication/principal/OpenIdService.java new file mode 100644 index 000000000000..7933fc216721 --- /dev/null +++ b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/authentication/principal/OpenIdService.java @@ -0,0 +1,219 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.authentication.principal; + +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.principal.AbstractWebApplicationService; +import org.jasig.cas.authentication.principal.DefaultResponse; +import org.jasig.cas.authentication.principal.Response; +import org.jasig.cas.ticket.TicketException; +import org.jasig.cas.util.ApplicationContextProvider; +import org.openid4java.association.Association; +import org.openid4java.message.AuthRequest; +import org.openid4java.message.Message; +import org.openid4java.message.MessageException; +import org.openid4java.message.ParameterList; +import org.openid4java.server.ServerManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Scott Battaglia + * @since 3.1 + */ +public final class OpenIdService extends AbstractWebApplicationService { + + /** The Constant LOGGER. */ + protected static final Logger LOGGER = LoggerFactory.getLogger(OpenIdService.class); + + private static final long serialVersionUID = 5776500133123291301L; + + private static final String CONST_PARAM_SERVICE = "openid.return_to"; + + private final String identity; + + private final String artifactId; + + private final ParameterList requestParameters; + + /** + * Instantiates a new OpenID service. + * + * @param id the id + * @param originalUrl the original url + * @param artifactId the artifact id + * @param openIdIdentity the OpenID identity + * @param signature the signature + * @param parameterList the parameter list + */ + protected OpenIdService(final String id, final String originalUrl, + final String artifactId, final String openIdIdentity, + final String signature, final ParameterList parameterList) { + super(id, originalUrl, artifactId); + this.identity = openIdIdentity; + this.artifactId = artifactId; + this.requestParameters = parameterList; + } + + /** + * Generates an Openid response. + * If no ticketId is found, response is negative. + * If we have a ticket id, then we check if we have an association. + * If so, we ask OpenId server manager to generate the answer according with the existing association. + * If not, we send back an answer with the ticket id as association handle. + * This will force the consumer to ask a verification, which will validate the service ticket. + * @param ticketId the service ticket to provide to the service. + * @return the generated authentication answer + */ + @Override + public Response getResponse(final String ticketId) { + final Map parameters = new HashMap<>(); + if (ticketId != null) { + + final ServerManager manager = (ServerManager) ApplicationContextProvider.getApplicationContext().getBean("serverManager"); + final CentralAuthenticationService cas = ApplicationContextProvider.getApplicationContext() + .getBean("centralAuthenticationService", CentralAuthenticationService.class); + boolean associated = false; + boolean associationValid = true; + try { + final AuthRequest authReq = AuthRequest.createAuthRequest(requestParameters, manager.getRealmVerifier()); + final Map parameterMap = authReq.getParameterMap(); + if (parameterMap != null && parameterMap.size() > 0) { + final String assocHandle = (String) parameterMap.get("openid.assoc_handle"); + if (assocHandle != null) { + final Association association = manager.getSharedAssociations().load(assocHandle); + if (association != null) { + associated = true; + if (association.hasExpired()) { + associationValid = false; + } + } + + } + } + } catch (final MessageException me) { + LOGGER.error("Message exception : {}", me.getMessage(), me); + } + + boolean successFullAuthentication = true; + try { + if (associated) { + if (associationValid) { + cas.validateServiceTicket(ticketId, this); + LOGGER.info("Validated openid ticket"); + } else { + successFullAuthentication = false; + } + } + } catch (final TicketException te) { + LOGGER.error("Could not validate ticket : {}", te.getMessage(), te); + successFullAuthentication = false; + } + + // We sign directly (final 'true') because we don't add extensions + // response message can be either a DirectError or an AuthSuccess here. + // Anyway, handling is the same : send the response message + final Message response = manager.authResponse(requestParameters, + this.identity, + this.identity, + successFullAuthentication, + true); + parameters.putAll(response.getParameterMap()); + if (!associated) { + parameters.put("openid.assoc_handle", ticketId); + } + } else { + parameters.put("openid.mode", "cancel"); + } + return DefaultResponse.getRedirectResponse(getOriginalUrl(), parameters); + } + + /** + * Return that the service is already logged out. + * + * @return that the service is already logged out. + */ + @Override + public boolean isLoggedOutAlready() { + return true; + } + + /** + * Creates the service from the request. + * + * @param request the request + * @return the OpenID service + */ + public static OpenIdService createServiceFrom( + final HttpServletRequest request) { + final String service = request.getParameter(CONST_PARAM_SERVICE); + final String openIdIdentity = request.getParameter("openid.identity"); + final String signature = request.getParameter("openid.sig"); + + if (openIdIdentity == null || !StringUtils.hasText(service)) { + return null; + } + + final String id = cleanupUrl(service); + final String artifactId = request.getParameter("openid.assoc_handle"); + final ParameterList paramList = new ParameterList(request.getParameterMap()); + + return new OpenIdService(id, service, artifactId, openIdIdentity, + signature, paramList); + } + + @Override + public int hashCode() { + return new HashCodeBuilder() + .append(this.identity) + .toHashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final OpenIdService other = (OpenIdService) obj; + if (this.identity == null) { + if (other.identity != null) { + return false; + } + } else if (!this.identity.equals(other.identity)) { + return false; + } + return true; + } + + public String getIdentity() { + return this.identity; + } +} diff --git a/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/OpenIdProviderController.java b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/OpenIdProviderController.java new file mode 100644 index 000000000000..13ea57e23a0c --- /dev/null +++ b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/OpenIdProviderController.java @@ -0,0 +1,49 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.web; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; + +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.AbstractController; + +/** + * Maps requests for usernames to a page that displays the Login URL for an + * OpenId Identity Provider. + * + * @author Scott Battaglia + * @since 3.1 + */ +public final class OpenIdProviderController extends AbstractController { + + @NotNull + private String loginUrl; + + @Override + protected ModelAndView handleRequestInternal(final HttpServletRequest request, + final HttpServletResponse response) throws Exception { + return new ModelAndView("openIdProviderView", "openid_server", this.loginUrl); + } + + public void setLoginUrl(final String loginUrl) { + this.loginUrl = loginUrl; + } +} diff --git a/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/flow/OpenIdSingleSignOnAction.java b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/flow/OpenIdSingleSignOnAction.java new file mode 100644 index 000000000000..6be1f721b186 --- /dev/null +++ b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/flow/OpenIdSingleSignOnAction.java @@ -0,0 +1,74 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.web.flow; + +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.support.openid.authentication.principal.OpenIdCredential; +import org.jasig.cas.support.openid.authentication.principal.OpenIdService; +import org.jasig.cas.support.openid.web.support.DefaultOpenIdUserNameExtractor; +import org.jasig.cas.support.openid.web.support.OpenIdUserNameExtractor; +import org.jasig.cas.web.flow.AbstractNonInteractiveCredentialsAction; +import org.jasig.cas.web.support.WebUtils; + +import org.springframework.webflow.execution.RequestContext; + +import javax.validation.constraints.NotNull; + +/** + * Attempts to utilize an existing single sign on session, but only if the + * Principal of the existing session matches the new Principal. Note that care + * should be taken when using credentials that are automatically provided and + * not entered by the user. + * + * @author Scott Battaglia + * @since 3.1 + */ +public final class OpenIdSingleSignOnAction extends AbstractNonInteractiveCredentialsAction { + + @NotNull + private OpenIdUserNameExtractor extractor = new DefaultOpenIdUserNameExtractor(); + + public void setExtractor(final OpenIdUserNameExtractor extractor) { + this.extractor = extractor; + } + + @Override + protected Credential constructCredentialsFromRequest(final RequestContext context) { + final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context); + final String userName = this.extractor + .extractLocalUsernameFromUri(context.getRequestParameters() + .get("openid.identity")); + final Service service = WebUtils.getService(context); + + context.getExternalContext().getSessionMap().put("openIdLocalId", userName); + + // clear the service because otherwise we can fake the username + if (service instanceof OpenIdService && userName == null) { + context.getFlowScope().remove("service"); + } + + if (ticketGrantingTicketId == null || userName == null) { + return null; + } + + return new OpenIdCredential( + ticketGrantingTicketId, userName); + } +} diff --git a/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/mvc/SmartOpenIdController.java b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/mvc/SmartOpenIdController.java new file mode 100644 index 000000000000..25319f90d12d --- /dev/null +++ b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/mvc/SmartOpenIdController.java @@ -0,0 +1,127 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.web.mvc; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.web.DelegateController; +import org.openid4java.message.Message; +import org.openid4java.message.ParameterList; +import org.openid4java.server.ServerManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * Generates an association to an openid association request. + * @author Frederic Esnault + * @since 3.5 + */ +public class SmartOpenIdController extends DelegateController implements Serializable { + + private static final long serialVersionUID = -594058549445950430L; + + /** View if association Fails. */ + private static final String DEFAULT_ASSOCIATION_FAILURE_VIEW_NAME = "casOpenIdAssociationFailureView"; + + /** View if association Succeeds. */ + private static final String DEFAULT_ASSOCIATION_SUCCESS_VIEW_NAME = "casOpenIdAssociationSuccessView"; + + private static final String ASSOCIATE = "associate"; + + private final Logger logger = LoggerFactory.getLogger(SmartOpenIdController.class); + + private ServerManager serverManager; + + /** The view to redirect to on a successful validation. */ + @NotNull + private String successView = DEFAULT_ASSOCIATION_SUCCESS_VIEW_NAME; + + /** The view to redirect to on a validation failure. Not used for now, + * the succes view may return failed association attemps. No need for another view. */ + @NotNull + private String failureView = DEFAULT_ASSOCIATION_FAILURE_VIEW_NAME; + + /** + * Gets the association response. Determines the mode first. + * If mode is set to associate, will set the response. Then + * builds the response parameters next and returns. + * + * @param request the request + * @return the association response + */ + public Map getAssociationResponse(final HttpServletRequest request) { + final ParameterList parameters = new ParameterList(request.getParameterMap()); + + final String mode = parameters.hasParameter("openid.mode") + ? parameters.getParameterValue("openid.mode") + : null; + + Message response = null; + + if (StringUtils.equals(mode, ASSOCIATE)) { + response = serverManager.associationResponse(parameters); + } + final Map responseParams = new HashMap<>(); + if (response != null) { + responseParams.putAll(response.getParameterMap()); + } + + return responseParams; + + } + + @Override + protected ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + final Map parameters = new HashMap<>(); + parameters.putAll(getAssociationResponse(request)); + return new ModelAndView(successView, "parameters", parameters); + } + + @Override + public boolean canHandle(final HttpServletRequest request, final HttpServletResponse response) { + final String openIdMode = request.getParameter("openid.mode"); + if (StringUtils.equals(openIdMode, ASSOCIATE)) { + logger.info("Handling request. openid.mode : {}", openIdMode); + return true; + } + logger.info("Cannot handle request. openid.mode : {}", openIdMode); + return false; + } + + public void setSuccessView(final String successView) { + this.successView = successView; + } + + public void setFailureView(final String failureView) { + this.failureView = failureView; + } + + @NotNull + public void setServerManager(final ServerManager serverManager) { + this.serverManager = serverManager; + } +} diff --git a/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/support/DefaultOpenIdUserNameExtractor.java b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/support/DefaultOpenIdUserNameExtractor.java new file mode 100644 index 000000000000..ee0e84997f30 --- /dev/null +++ b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/support/DefaultOpenIdUserNameExtractor.java @@ -0,0 +1,46 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.web.support; + + +/** + * Extracts a local Id from an openid.identity. The default provider can extract + * the following uris: http://openid.myprovider.com/scottb provides a local id + * of scottb. + * + * @author Scott Battaglia + * @since 3.1 + */ +public final class DefaultOpenIdUserNameExtractor implements +OpenIdUserNameExtractor { + + @Override + public String extractLocalUsernameFromUri(final String uri) { + if (uri == null) { + return null; + } + + if (!uri.contains("/")) { + return null; + } + + return uri.substring(uri.lastIndexOf('/') + 1); + } + +} diff --git a/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/support/OpenIdArgumentExtractor.java b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/support/OpenIdArgumentExtractor.java new file mode 100644 index 000000000000..8162da333ffb --- /dev/null +++ b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/support/OpenIdArgumentExtractor.java @@ -0,0 +1,39 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.web.support; + +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.support.openid.authentication.principal.OpenIdService; +import org.jasig.cas.web.support.AbstractArgumentExtractor; + +import javax.servlet.http.HttpServletRequest; + +/** + * Constructs an OpenId Service. + * + * @author Scott Battaglia + * @since 3.1 + */ +public class OpenIdArgumentExtractor extends AbstractArgumentExtractor { + + @Override + protected WebApplicationService extractServiceInternal(final HttpServletRequest request) { + return OpenIdService.createServiceFrom(request); + } +} diff --git a/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/support/OpenIdPostUrlHandlerMapping.java b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/support/OpenIdPostUrlHandlerMapping.java new file mode 100644 index 000000000000..779278a16158 --- /dev/null +++ b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/support/OpenIdPostUrlHandlerMapping.java @@ -0,0 +1,47 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.web.support; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; + +/** + * + * @author Scott Battaglia + * @since 3.1 + * + */ +public final class OpenIdPostUrlHandlerMapping extends SimpleUrlHandlerMapping { + + @Override + protected Object lookupHandler(final String urlPath, final HttpServletRequest request) throws Exception { + + if ("POST".equals(request.getMethod()) + && ( + "check_authentication".equals(request.getParameter("openid.mode")) + || "associate".equals(request.getParameter("openid.mode")) + ) + ) { + return super.lookupHandler(urlPath, request); + } + + return null; + } +} diff --git a/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/support/OpenIdUserNameExtractor.java b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/support/OpenIdUserNameExtractor.java new file mode 100644 index 000000000000..f9129468e85c --- /dev/null +++ b/cas-server-support-openid/src/main/java/org/jasig/cas/support/openid/web/support/OpenIdUserNameExtractor.java @@ -0,0 +1,35 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.web.support; + +/** + * + * @author Scott Battaglia + * @since 3.1 + */ +public interface OpenIdUserNameExtractor { + + /** + * Extract local username from uri. + * + * @param uri the uri + * @return the username + */ + String extractLocalUsernameFromUri(String uri); +} diff --git a/cas-server-support-openid/src/site/site.xml b/cas-server-support-openid/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-support-openid/src/site/site.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/cas-server-3.4.2/cas-server-support-openid/src/test/clover/clover.license b/cas-server-support-openid/src/test/clover/clover.license similarity index 100% rename from cas-server-3.4.2/cas-server-support-openid/src/test/clover/clover.license rename to cas-server-support-openid/src/test/clover/clover.license diff --git a/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/authentication/handler/support/OpenIdCredentialsAuthenticationHandlerTests.java b/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/authentication/handler/support/OpenIdCredentialsAuthenticationHandlerTests.java new file mode 100644 index 000000000000..229075fea1bc --- /dev/null +++ b/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/authentication/handler/support/OpenIdCredentialsAuthenticationHandlerTests.java @@ -0,0 +1,90 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.authentication.handler.support; + +import javax.security.auth.login.FailedLoginException; + +import static org.junit.Assert.*; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.support.openid.authentication.principal.OpenIdCredential; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.jasig.cas.ticket.registry.DefaultTicketRegistry; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Scott Battaglia + * @since 3.1 + */ +public class OpenIdCredentialsAuthenticationHandlerTests { + + private OpenIdCredentialsAuthenticationHandler openIdCredentialsAuthenticationHandler; + + private TicketRegistry ticketRegistry; + + @Before + public void setUp() throws Exception { + this.openIdCredentialsAuthenticationHandler = new OpenIdCredentialsAuthenticationHandler(); + this.ticketRegistry = new DefaultTicketRegistry(); + this.openIdCredentialsAuthenticationHandler.setTicketRegistry(this.ticketRegistry); + } + + @Test + public void verifySupports() { + assertTrue(this.openIdCredentialsAuthenticationHandler.supports(new OpenIdCredential("test", "test"))); + assertFalse(this.openIdCredentialsAuthenticationHandler.supports(new UsernamePasswordCredential())); + } + + @Test + public void verifyTGTWithSameId() throws Exception { + final OpenIdCredential c = new OpenIdCredential("test", "test"); + final TicketGrantingTicket t = getTicketGrantingTicket(); + this.ticketRegistry.addTicket(t); + + assertEquals("test", this.openIdCredentialsAuthenticationHandler.authenticate(c).getPrincipal().getId()); + } + + @Test(expected = FailedLoginException.class) + public void verifyTGTThatIsExpired() throws Exception { + final OpenIdCredential c = new OpenIdCredential("test", "test"); + final TicketGrantingTicket t = getTicketGrantingTicket(); + this.ticketRegistry.addTicket(t); + + t.markTicketExpired(); + this.openIdCredentialsAuthenticationHandler.authenticate(c); + } + + @Test(expected = FailedLoginException.class) + public void verifyTGTWithDifferentId() throws Exception { + final OpenIdCredential c = new OpenIdCredential("test", "test1"); + final TicketGrantingTicket t = getTicketGrantingTicket(); + this.ticketRegistry.addTicket(t); + + this.openIdCredentialsAuthenticationHandler.authenticate(c); + } + + protected TicketGrantingTicket getTicketGrantingTicket() { + return new TicketGrantingTicketImpl("test", TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + } +} diff --git a/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/authentication/principal/OpenIdServiceTests.java b/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/authentication/principal/OpenIdServiceTests.java new file mode 100644 index 000000000000..273a2e20aba8 --- /dev/null +++ b/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/authentication/principal/OpenIdServiceTests.java @@ -0,0 +1,160 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.authentication.principal; + +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.principal.Response; +import org.jasig.cas.util.ApplicationContextProvider; +import org.junit.Before; +import org.junit.Test; +import org.openid4java.association.Association; +import org.openid4java.server.ServerAssociationStore; +import org.openid4java.server.ServerManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.mock.web.MockHttpServletRequest; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Scott Battaglia + * @since 3.1 + */ +public class OpenIdServiceTests { + + private static final Logger LOGGER = LoggerFactory.getLogger(OpenIdServiceTests.class); + + private OpenIdService openIdService; + private ApplicationContext context; + private CentralAuthenticationService cas; + private ServerManager manager; + private ServerAssociationStore sharedAssociations; + private final MockHttpServletRequest request = new MockHttpServletRequest(); + + @Before + public void setUp() throws Exception { + request.addParameter("openid.identity", "http://openid.ja-sig.org/battags"); + request.addParameter("openid.return_to", "http://www.ja-sig.org/?service=fa"); + request.addParameter("openid.mode", "checkid_setup"); + sharedAssociations = mock(ServerAssociationStore.class); + manager = new ServerManager(); + manager.setOPEndpointUrl("https://localshot:8443/cas/login"); + manager.setEnforceRpId(false); + manager.setSharedAssociations(sharedAssociations); + context = mock(ApplicationContext.class); + cas = mock(CentralAuthenticationService.class); + + when(context.getBean("serverManager")).thenReturn(manager); + when(context.getBean("centralAuthenticationService", CentralAuthenticationService.class)).thenReturn(cas); + + final ApplicationContextProvider contextProvider = new ApplicationContextProvider(); + contextProvider.setApplicationContext(context); + } + + @Test + public void verifyGetResponse() { + openIdService = OpenIdService.createServiceFrom(request); + final Response response = this.openIdService.getResponse("test"); + try { + verify(cas, never()).validateServiceTicket("test", openIdService); + } catch (final Exception e) { + LOGGER.debug("Exception during verification of service ticket", e); + } + assertNotNull(response); + + assertEquals("test", response.getAttributes().get("openid.assoc_handle")); + assertEquals("http://www.ja-sig.org/?service=fa", response.getAttributes().get("openid.return_to")); + assertEquals("http://openid.ja-sig.org/battags", response.getAttributes().get("openid.identity")); + + final Response response2 = this.openIdService.getResponse(null); + assertEquals("cancel", response2.getAttributes().get("openid.mode")); + } + + @Test + public void verifySmartModeGetResponse() { + request.addParameter("openid.assoc_handle", "test"); + openIdService = OpenIdService.createServiceFrom(request); + Association association = null; + try { + association = Association.generate(Association.TYPE_HMAC_SHA1, "test", 60); + } catch (final Exception e) { + fail("Could not generate association"); + } + when(sharedAssociations.load("test")).thenReturn(association); + final Response response = this.openIdService.getResponse("test"); + try { + verify(cas).validateServiceTicket("test", openIdService); + } catch (final Exception e) { + fail("Error while validating ticket"); + } + + request.removeParameter("openid.assoc_handle"); + assertNotNull(response); + + assertEquals("test", response.getAttributes().get("openid.assoc_handle")); + assertEquals("http://www.ja-sig.org/?service=fa", response.getAttributes().get("openid.return_to")); + assertEquals("http://openid.ja-sig.org/battags", response.getAttributes().get("openid.identity")); + } + + @Test + public void verifyExpiredAssociationGetResponse() { + request.addParameter("openid.assoc_handle", "test"); + openIdService = OpenIdService.createServiceFrom(request); + Association association = null; + try { + association = Association.generate(Association.TYPE_HMAC_SHA1, "test", 2); + } catch (final Exception e) { + fail("Could not generate association"); + } + when(sharedAssociations.load("test")).thenReturn(association); + synchronized (this) { + try { + this.wait(3000); + } catch (final InterruptedException ie) { + fail("Could not wait long enough to check association expiry date"); + } + } + final Response response = this.openIdService.getResponse("test"); + request.removeParameter("openid.assoc_handle"); + assertNotNull(response); + + assertEquals(1, response.getAttributes().size()); + assertEquals("cancel", response.getAttributes().get("openid.mode")); + } + + @Test + public void verifyEquals() { + final MockHttpServletRequest request1 = new MockHttpServletRequest(); + request1.addParameter("openid.identity", "http://openid.ja-sig.org/battags"); + request1.addParameter("openid.return_to", "http://www.ja-sig.org/?service=fa"); + request1.addParameter("openid.mode", "openid.checkid_setup"); + + final MockHttpServletRequest request2 = new MockHttpServletRequest(); + request2.addParameter("openid.identity", "http://openid.ja-sig.org/battags"); + request2.addParameter("openid.return_to", "http://www.ja-sig.org/?service=fa"); + + final OpenIdService o1 = OpenIdService.createServiceFrom(request1); + final OpenIdService o2 = OpenIdService.createServiceFrom(request2); + + assertTrue(o1.equals(o2)); + assertFalse(o1.equals(new Object())); + } +} diff --git a/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/web/flow/OpenIdSingleSignOnActionTests.java b/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/web/flow/OpenIdSingleSignOnActionTests.java new file mode 100644 index 000000000000..0d6b3c60e7c3 --- /dev/null +++ b/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/web/flow/OpenIdSingleSignOnActionTests.java @@ -0,0 +1,158 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.web.flow; + + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.jasig.cas.CentralAuthenticationServiceImpl; +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.logout.LogoutManager; +import org.jasig.cas.authentication.AuthenticationHandler; +import org.jasig.cas.authentication.AuthenticationManager; +import org.jasig.cas.authentication.PolicyBasedAuthenticationManager; +import org.jasig.cas.authentication.principal.PrincipalResolver; +import org.jasig.cas.services.DefaultServicesManagerImpl; +import org.jasig.cas.services.InMemoryServiceRegistryDaoImpl; +import org.jasig.cas.support.openid.authentication.handler.support.OpenIdCredentialsAuthenticationHandler; +import org.jasig.cas.support.openid.authentication.principal.OpenIdPrincipalResolver; +import org.jasig.cas.support.openid.authentication.principal.OpenIdService; +import org.jasig.cas.support.openid.web.support.DefaultOpenIdUserNameExtractor; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.jasig.cas.ticket.registry.DefaultTicketRegistry; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.test.MockRequestContext; + +/** + * @author Scott Battaglia + * @since 3.1 + */ +public class OpenIdSingleSignOnActionTests { + + private OpenIdSingleSignOnAction action; + + private TicketRegistry ticketRegistry; + + private AuthenticationManager authenticationManager; + + private CentralAuthenticationServiceImpl impl; + + @Before + public void setUp() throws Exception { + this.ticketRegistry = new DefaultTicketRegistry(); + final OpenIdCredentialsAuthenticationHandler handler = new OpenIdCredentialsAuthenticationHandler(); + handler.setTicketRegistry(this.ticketRegistry); + this.authenticationManager = new PolicyBasedAuthenticationManager( + Collections.singletonMap( + handler, + new OpenIdPrincipalResolver())); + + final Map generator = new HashMap<>(); + generator.put(OpenIdService.class.getName(), new DefaultUniqueTicketIdGenerator()); + + impl = new CentralAuthenticationServiceImpl(this.ticketRegistry, null, this.authenticationManager, + new DefaultUniqueTicketIdGenerator(), generator, new NeverExpiresExpirationPolicy(), + new NeverExpiresExpirationPolicy(), + new DefaultServicesManagerImpl(new InMemoryServiceRegistryDaoImpl()), mock(LogoutManager.class)); + + this.action = new OpenIdSingleSignOnAction(); + this.action.setCentralAuthenticationService(this.impl); + this.action.setExtractor(new DefaultOpenIdUserNameExtractor()); + this.action.afterPropertiesSet(); + } + + @Test + public void verifyNoTgt() throws Exception { + final MockRequestContext context = new MockRequestContext(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), new MockHttpServletRequest(), + new MockHttpServletResponse())); + assertEquals("error", this.action.execute(context).getId()); + } + + @Test + public void verifyNoService() throws Exception { + final MockRequestContext context = new MockRequestContext(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, + new MockHttpServletResponse())); + final Event event = this.action.execute(context); + + assertNotNull(event); + + assertEquals("error", this.action.execute(context).getId()); + } + + @Test + public void verifyBadUsername() throws Exception { + final MockRequestContext context = new MockRequestContext(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("openid.identity", "fablah"); + request.setParameter("openid.return_to", "http://www.cnn.com"); + + final OpenIdService service = OpenIdService.createServiceFrom(request); + context.getFlowScope().put("service", service); + context.getFlowScope().put("ticketGrantingTicketId", "tgtId"); + + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, + new MockHttpServletResponse())); + assertEquals("error", this.action.execute(context).getId()); + } + + @Test + public void verifySuccessfulServiceTicket() throws Exception { + final MockRequestContext context = new MockRequestContext(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + final Authentication authentication = TestUtils.getAuthentication("scootman28"); + final TicketGrantingTicket t = new TicketGrantingTicketImpl("TGT-11", authentication, + new NeverExpiresExpirationPolicy()); + + this.ticketRegistry.addTicket(t); + + request.setParameter("openid.identity", "http://openid.aol.com/scootman28"); + request.setParameter("openid.return_to", "http://www.cnn.com"); + + final OpenIdService service = OpenIdService.createServiceFrom(request); + context.getFlowScope().put("service", service); + context.getFlowScope().put("ticketGrantingTicketId", t.getId()); + + context.setExternalContext(new ServletExternalContext(new MockServletContext(), request, + new MockHttpServletResponse())); + assertEquals("success", this.action.execute(context).getId()); + } +} diff --git a/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/web/mvc/SmartOpenIdControllerTest.java b/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/web/mvc/SmartOpenIdControllerTest.java new file mode 100644 index 000000000000..22d0ccddb07b --- /dev/null +++ b/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/web/mvc/SmartOpenIdControllerTest.java @@ -0,0 +1,83 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.web.mvc; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; +import org.openid4java.server.ServerManager; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import javax.servlet.http.HttpServletResponse; + +import java.util.Map; + +/** + * Test case of the Smart OpenId Controller. + * @author Frederic Esnault + * @since 3.0.0 + */ +public class SmartOpenIdControllerTest { + private final MockHttpServletRequest request = new MockHttpServletRequest(); + private final HttpServletResponse response = new MockHttpServletResponse(); + private ServerManager manager; + private final SmartOpenIdController smartOpenIdController = new SmartOpenIdController(); + + @Before + public void setUp() { + manager = new ServerManager(); + manager.setOPEndpointUrl("https://localshot:8443/cas/login"); + manager.setEnforceRpId(false); + smartOpenIdController.setServerManager(manager); + } + + @Test + public void verifyCanHandle() { + request.addParameter("openid.mode", "associate"); + final boolean canHandle = smartOpenIdController.canHandle(request, response); + request.removeParameter("openid.mode"); + assertEquals(true, canHandle); + } + + @Test + public void verifyCannotHandle() { + request.addParameter("openid.mode", "anythingElse"); + final boolean canHandle = smartOpenIdController.canHandle(request, response); + request.removeParameter("openid.mode"); + assertEquals(false, canHandle); + } + + @Test + public void verifyGetAssociationResponse() { + request.addParameter("openid.mode", "associate"); + request.addParameter("openid.session_type", "DH-SHA1"); + request.addParameter("openid.assoc_type", "HMAC-SHA1"); + request.addParameter("openid.dh_consumer_public", + "NzKoFMyrzFn/5iJFPdX6MVvNA/BChV1/sJdnYbupDn7ptn+cerwEzyFfWFx25KsoLSkxQCaSMmYtc1GPy/2GI1BSKSDhpdJmDBb" + + "QRa/9Gs+giV/5fHcz/mHz8sREc7RTGI+0Ka9230arwrWt0fnoaJLRKEGUsmFR71rCo4EUOew="); + final Map assocResponse = smartOpenIdController.getAssociationResponse(request); + assertTrue(assocResponse.containsKey("assoc_handle")); + assertTrue(assocResponse.containsKey("expires_in")); + assertTrue(assocResponse.containsKey("dh_server_public")); + assertTrue(assocResponse.containsKey("enc_mac_key")); + request.removeParameter("openid.mode"); + } +} diff --git a/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/web/support/DefaultOpenIdUserNameExtractorTests.java b/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/web/support/DefaultOpenIdUserNameExtractorTests.java new file mode 100644 index 000000000000..12f8833db7ca --- /dev/null +++ b/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/web/support/DefaultOpenIdUserNameExtractorTests.java @@ -0,0 +1,51 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.web.support; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * @author Scott Battaglia + * @since 3.1 + + */ +public class DefaultOpenIdUserNameExtractorTests { + + private final DefaultOpenIdUserNameExtractor extractor = new DefaultOpenIdUserNameExtractor(); + + @Test + public void verifyExtractionSuccessful() { + assertEquals("scootman28", this.extractor + .extractLocalUsernameFromUri("http://test.com/scootman28")); + } + + @Test + public void verifyExtractionFailed() { + assertNull(this.extractor + .extractLocalUsernameFromUri("test.com")); + } + + @Test + public void verifyNull() { + assertNull(this.extractor + .extractLocalUsernameFromUri(null)); + } +} diff --git a/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/web/support/OpenIdArgumentExtractorTests.java b/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/web/support/OpenIdArgumentExtractorTests.java new file mode 100644 index 000000000000..8bd86776d7bf --- /dev/null +++ b/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/web/support/OpenIdArgumentExtractorTests.java @@ -0,0 +1,44 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.web.support; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +/** + * @author Scott Battaglia + * @since 3.1 + */ +public class OpenIdArgumentExtractorTests { + + private OpenIdArgumentExtractor extractor; + + @Before + public void setUp() throws Exception { + this.extractor = new OpenIdArgumentExtractor(); + } + + @Test + public void verifyNoOpenIdServiceExists() { + assertNull(this.extractor.extractService(new MockHttpServletRequest())); + } +} diff --git a/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/web/support/OpenIdPostUrlHandlerMappingTests.java b/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/web/support/OpenIdPostUrlHandlerMappingTests.java new file mode 100644 index 000000000000..8fa85e4af8bb --- /dev/null +++ b/cas-server-support-openid/src/test/java/org/jasig/cas/support/openid/web/support/OpenIdPostUrlHandlerMappingTests.java @@ -0,0 +1,90 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.openid.web.support; + +/** + * @author Scott Battaglia + * @since 3.1 + */ +public class OpenIdPostUrlHandlerMappingTests { + + private OpenIdPostUrlHandlerMapping handlerMapping; + + public void verifyTest() { + } + + /* + protected void setUp() throws Exception { + final GenericWebApplicationContext context = new GenericWebApplicationContext(); + context.refresh(); + final RootBeanDefinition definition = new RootBeanDefinition(Object.class); + context.registerBeanDefinition("testHandler", definition); + + context.start(); + + final Map properties = new HashMap<>(); + properties.put("/login", new Object()); + + this.handlerMapping = new OpenIdPostUrlHandlerMapping(); + this.handlerMapping.setUrlMap(properties); + + this.handlerMapping.initApplicationContext(); + } + + + public void verifyNoMatch() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setContextPath("/hello"); + + assertNull(this.handlerMapping.lookupHandler("/hello", request)); + } + + public void verifyImproperMatch() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setContextPath("/hello"); + + assertNull(this.handlerMapping.lookupHandler("/login", request)); + } + + public void verifyProperMatchWrongMethod() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setContextPath("/login"); + request.setMethod("GET"); + + assertNull(this.handlerMapping.lookupHandler("/login", request)); + } + + public void verifyProperMatchCorrectMethodNoParam() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setContextPath("/login"); + request.setMethod("POST"); + + assertNull(this.handlerMapping.lookupHandler("/login", request)); + } + + public void verifyProperMatchCorrectMethodWithParam() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setContextPath("/login"); + request.setMethod("POST"); + request.setParameter("openid.mode", "check_authentication"); + + + assertNotNull(this.handlerMapping.lookupHandler("/login", request)); + }*/ +} diff --git a/cas-server-support-pac4j/NOTICE b/cas-server-support-pac4j/NOTICE new file mode 100644 index 000000000000..c5f104550db6 --- /dev/null +++ b/cas-server-support-pac4j/NOTICE @@ -0,0 +1,105 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apereo CAS Client Protocols Support using pac4j under Apache 2 + Apereo CAS Core under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + CDI APIs under Apache License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + pac4j core under The Apache Software License, Version 2.0 + pac4j for HTTP protocol under The Apache Software License, Version 2.0 + pac4j for OAuth protocol under The Apache Software License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + Scribe OAuth Library under MIT + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-support-pac4j/pom.xml b/cas-server-support-pac4j/pom.xml new file mode 100644 index 000000000000..af56236ae589 --- /dev/null +++ b/cas-server-support-pac4j/pom.xml @@ -0,0 +1,76 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-support-pac4j + jar + Apereo CAS Client Protocols Support using pac4j + + + + leleuj + Jérôme Leleu + leleuj@gmail.com + -5 + + developer + maintainer + + + + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + org.pac4j + pac4j-core + ${pac4j.version} + + + + org.pac4j + pac4j-oauth + test + ${pac4j.version} + + + + org.pac4j + pac4j-http + test + ${pac4j.version} + + + + + ${project.parent.basedir} + + diff --git a/cas-server-support-pac4j/src/main/java/org/jasig/cas/support/pac4j/authentication/ClientAuthenticationMetaDataPopulator.java b/cas-server-support-pac4j/src/main/java/org/jasig/cas/support/pac4j/authentication/ClientAuthenticationMetaDataPopulator.java new file mode 100644 index 000000000000..8d778e8cf886 --- /dev/null +++ b/cas-server-support-pac4j/src/main/java/org/jasig/cas/support/pac4j/authentication/ClientAuthenticationMetaDataPopulator.java @@ -0,0 +1,53 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.pac4j.authentication; + +import org.jasig.cas.authentication.AuthenticationBuilder; +import org.jasig.cas.authentication.AuthenticationMetaDataPopulator; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.support.pac4j.authentication.principal.ClientCredential; + +/** + * This class is a meta data populator for authentication. The client name associated to the authentication is added + * to the authentication attributes. + * + * @author Jerome Leleu + * @since 3.5.0 + */ +public final class ClientAuthenticationMetaDataPopulator implements AuthenticationMetaDataPopulator { + + /*** + * The name of the client used to perform the authentication. + */ + public static final String CLIENT_NAME = "clientName"; + + /** + * {@inheritDoc} + */ + @Override + public void populateAttributes(final AuthenticationBuilder builder, final Credential credential) { + final ClientCredential clientCredential = (ClientCredential) credential; + builder.addAttribute(CLIENT_NAME, clientCredential.getCredentials().getClientName()); + } + + @Override + public boolean supports(final Credential credential) { + return credential instanceof ClientCredential; + } +} diff --git a/cas-server-support-pac4j/src/main/java/org/jasig/cas/support/pac4j/authentication/handler/support/AbstractClientAuthenticationHandler.java b/cas-server-support-pac4j/src/main/java/org/jasig/cas/support/pac4j/authentication/handler/support/AbstractClientAuthenticationHandler.java new file mode 100644 index 000000000000..30815c6cc59f --- /dev/null +++ b/cas-server-support-pac4j/src/main/java/org/jasig/cas/support/pac4j/authentication/handler/support/AbstractClientAuthenticationHandler.java @@ -0,0 +1,121 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.pac4j.authentication.handler.support; + +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.jasig.cas.authentication.principal.PrincipalFactory; +import org.jasig.cas.support.pac4j.authentication.principal.ClientCredential; +import org.pac4j.core.client.Client; +import org.pac4j.core.client.Clients; +import org.pac4j.core.context.J2EContext; +import org.pac4j.core.context.WebContext; +import org.pac4j.core.credentials.Credentials; +import org.pac4j.core.profile.UserProfile; +import org.springframework.webflow.context.ExternalContextHolder; +import org.springframework.webflow.context.servlet.ServletExternalContext; + +import javax.security.auth.login.FailedLoginException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; +import java.security.GeneralSecurityException; + +/** + * Generic handler for delegated authentication: it uses the client credentials to get the user profile + * returned by the provider for an authenticated user. + * + * @author Jerome Leleu + * @since 4.1.0 + */ +@SuppressWarnings("unchecked") +public abstract class AbstractClientAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler { + + /** Factory to create the principal type. **/ + @NotNull + protected PrincipalFactory principalFactory = new DefaultPrincipalFactory(); + + /** + * The clients for authentication. + */ + @NotNull + private final Clients clients; + + /** + * Define the clients. + * + * @param theClients The clients for authentication + */ + public AbstractClientAuthenticationHandler(final Clients theClients) { + this.clients = theClients; + } + + @Override + public final boolean supports(final Credential credential) { + return credential != null && ClientCredential.class.isAssignableFrom(credential.getClass()); + } + + @Override + protected HandlerResult doAuthentication(final Credential credential) throws GeneralSecurityException, PreventedException { + final ClientCredential clientCredentials = (ClientCredential) credential; + logger.debug("clientCredentials : {}", clientCredentials); + + final String clientName = clientCredentials.getCredentials().getClientName(); + logger.debug("clientName : {}", clientName); + + // get client + final Client client = this.clients.findClient(clientName); + logger.debug("client : {}", client); + + // web context + final ServletExternalContext servletExternalContext = (ServletExternalContext) ExternalContextHolder.getExternalContext(); + final HttpServletRequest request = (HttpServletRequest) servletExternalContext.getNativeRequest(); + final HttpServletResponse response = (HttpServletResponse) servletExternalContext.getNativeResponse(); + final WebContext webContext = new J2EContext(request, response); + + // get user profile + final UserProfile userProfile = client.getUserProfile(clientCredentials.getCredentials(), webContext); + logger.debug("userProfile : {}", userProfile); + + if (userProfile != null) { + return createResult(clientCredentials, userProfile); + } + + throw new FailedLoginException("Provider did not produce a user profile for: " + clientCredentials); + } + + /** + * Build the handler result. + * + * @param credentials the provided credentials + * @param profile the retrieved user profile + * @return the built handler result + * @throws GeneralSecurityException On authentication failure. + * @throws PreventedException On the indeterminate case when authentication is prevented. + */ + protected abstract HandlerResult createResult(final ClientCredential credentials, final UserProfile profile) + throws GeneralSecurityException, PreventedException; + + public void setPrincipalFactory(final PrincipalFactory principalFactory) { + this.principalFactory = principalFactory; + } +} diff --git a/cas-server-support-pac4j/src/main/java/org/jasig/cas/support/pac4j/authentication/handler/support/ClientAuthenticationHandler.java b/cas-server-support-pac4j/src/main/java/org/jasig/cas/support/pac4j/authentication/handler/support/ClientAuthenticationHandler.java new file mode 100644 index 000000000000..3b20273309a1 --- /dev/null +++ b/cas-server-support-pac4j/src/main/java/org/jasig/cas/support/pac4j/authentication/handler/support/ClientAuthenticationHandler.java @@ -0,0 +1,84 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.pac4j.authentication.handler.support; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.authentication.BasicCredentialMetaData; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.support.pac4j.authentication.principal.ClientCredential; +import org.pac4j.core.client.Clients; +import org.pac4j.core.profile.UserProfile; + +import javax.security.auth.login.FailedLoginException; +import java.security.GeneralSecurityException; + +/** + * Specialized handler which builds the authenticated user directly from the retrieved user profile. + * + * @author Jerome Leleu + * @since 3.5.0 + */ +public class ClientAuthenticationHandler extends AbstractClientAuthenticationHandler { + + /** + * Whether to use the typed identifier (by default) or just the identifier. + */ + private boolean typedIdUsed = true; + + /** + * Define the clients. + * + * @param theClients The clients for authentication + */ + public ClientAuthenticationHandler(final Clients theClients) { + super(theClients); + } + + /** + * {@inheritDoc} + */ + @Override + protected HandlerResult createResult(final ClientCredential credentials, final UserProfile profile) + throws GeneralSecurityException, PreventedException { + final String id; + if (typedIdUsed) { + id = profile.getTypedId(); + } else { + id = profile.getId(); + } + if (StringUtils.isNotBlank(id)) { + credentials.setUserProfile(profile); + return new DefaultHandlerResult( + this, + new BasicCredentialMetaData(credentials), + this.principalFactory.createPrincipal(id, profile.getAttributes())); + } + throw new FailedLoginException("No identifier found for this user profile: " + profile); + } + + public boolean isTypedIdUsed() { + return typedIdUsed; + } + + public void setTypedIdUsed(final boolean typedIdUsed) { + this.typedIdUsed = typedIdUsed; + } +} diff --git a/cas-server-support-pac4j/src/main/java/org/jasig/cas/support/pac4j/authentication/principal/ClientCredential.java b/cas-server-support-pac4j/src/main/java/org/jasig/cas/support/pac4j/authentication/principal/ClientCredential.java new file mode 100644 index 000000000000..7cee19c7a639 --- /dev/null +++ b/cas-server-support-pac4j/src/main/java/org/jasig/cas/support/pac4j/authentication/principal/ClientCredential.java @@ -0,0 +1,92 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.pac4j.authentication.principal; + +import java.io.Serializable; + +import org.jasig.cas.authentication.Credential; +import org.pac4j.core.profile.UserProfile; + +/** + * This class represents client credentials and (after authentication) a user profile. + * + * @author Jerome Leleu + * @since 3.5.0 + */ +public final class ClientCredential implements Credential, Serializable { + + /** + * The servialVersionUID. + */ + private static final long serialVersionUID = -7883301304291894763L; + + /** + * The user profile after authentication. + */ + private UserProfile userProfile; + + /** + * The internal credentials provided by the authentication at the provider. + */ + private final org.pac4j.core.credentials.Credentials credentials; + + /** + * Define the credentials. + * + * @param theCredentials The authentication credentials + */ + public ClientCredential(final org.pac4j.core.credentials.Credentials theCredentials) { + this.credentials = theCredentials; + } + + /** + * Return the credentials. + * + * @return the credentials + */ + public org.pac4j.core.credentials.Credentials getCredentials() { + return credentials; + } + + /** + * Return the profile of the authenticated user. + * + * @return the profile of the authenticated user + */ + public UserProfile getUserProfile() { + return userProfile; + } + + /** + * Define the user profile. + * + * @param theUserProfile The user profile + */ + public void setUserProfile(final UserProfile theUserProfile) { + this.userProfile = theUserProfile; + } + + @Override + public String getId() { + if (this.userProfile != null) { + return this.userProfile.getTypedId(); + } + return null; + } +} diff --git a/cas-server-support-pac4j/src/main/java/org/jasig/cas/support/pac4j/web/flow/ClientAction.java b/cas-server-support-pac4j/src/main/java/org/jasig/cas/support/pac4j/web/flow/ClientAction.java new file mode 100644 index 000000000000..112ebe3f8b8e --- /dev/null +++ b/cas-server-support-pac4j/src/main/java/org/jasig/cas/support/pac4j/web/flow/ClientAction.java @@ -0,0 +1,245 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.pac4j.web.flow; + +import java.util.Set; + +import com.google.common.collect.ImmutableSet; +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.support.pac4j.authentication.principal.ClientCredential; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.web.support.WebUtils; +import org.pac4j.core.client.BaseClient; +import org.pac4j.core.client.Client; +import org.pac4j.core.client.Clients; +import org.pac4j.core.client.Mechanism; +import org.pac4j.core.context.J2EContext; +import org.pac4j.core.context.WebContext; +import org.pac4j.core.credentials.Credentials; +import org.pac4j.core.exception.RequiresHttpAction; +import org.pac4j.core.exception.TechnicalException; +import org.pac4j.core.profile.CommonProfile; +import org.pac4j.core.profile.ProfileHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.webflow.action.AbstractAction; +import org.springframework.webflow.context.ExternalContext; +import org.springframework.webflow.context.ExternalContextHolder; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.validation.constraints.NotNull; + +/** + * This class represents an action to put at the beginning of the webflow. + *

+ * Before any authentication, redirection urls are computed for the different clients defined as well as the theme, + * locale, method and service are saved into the web session.

+ * After authentication, appropriate information are expected on this callback url to finish the authentication + * process with the provider. + * @author Jerome Leleu + * @since 3.5.0 + */ +@SuppressWarnings({ "unchecked", "rawtypes" }) +public final class ClientAction extends AbstractAction { + /** + * Constant for the service parameter. + */ + public static final String SERVICE = "service"; + /** + * Constant for the theme parameter. + */ + public static final String THEME = "theme"; + /** + * Constant for the locale parameter. + */ + public static final String LOCALE = "locale"; + /** + * Constant for the method parameter. + */ + public static final String METHOD = "method"; + /** + * Supported protocols. + */ + private static final Set SUPPORTED_PROTOCOLS = ImmutableSet.of(Mechanism.CAS_PROTOCOL, Mechanism.OAUTH_PROTOCOL, + Mechanism.OPENID_PROTOCOL, Mechanism.SAML_PROTOCOL, Mechanism.OPENID_CONNECT_PROTOCOL); + + /** + * The logger. + */ + private final Logger logger = LoggerFactory.getLogger(ClientAction.class); + + /** + * The clients used for authentication. + */ + @NotNull + private final Clients clients; + + /** + * The service for CAS authentication. + */ + @NotNull + private final CentralAuthenticationService centralAuthenticationService; + + /** + * Build the action. + * + * @param theCentralAuthenticationService The service for CAS authentication + * @param theClients The clients for authentication + */ + public ClientAction(final CentralAuthenticationService theCentralAuthenticationService, + final Clients theClients) { + this.centralAuthenticationService = theCentralAuthenticationService; + this.clients = theClients; + ProfileHelper.setKeepRawData(true); + } + + /** + * {@inheritDoc} + */ + @Override + protected Event doExecute(final RequestContext context) throws Exception { + final HttpServletRequest request = WebUtils.getHttpServletRequest(context); + final HttpServletResponse response = WebUtils.getHttpServletResponse(context); + final HttpSession session = request.getSession(); + + // web context + final WebContext webContext = new J2EContext(request, response); + + // get client + final String clientName = request.getParameter(this.clients.getClientNameParameter()); + logger.debug("clientName: {}", clientName); + + // it's an authentication + if (StringUtils.isNotBlank(clientName)) { + // get client + final BaseClient client = + (BaseClient) this.clients + .findClient(clientName); + logger.debug("client: {}", client); + + // Only supported protocols + final Mechanism mechanism = client.getMechanism(); + if (!SUPPORTED_PROTOCOLS.contains(mechanism)) { + throw new TechnicalException("Only CAS, OAuth, OpenID and SAML protocols are supported: " + client); + } + + // get credentials + final Credentials credentials; + try { + credentials = client.getCredentials(webContext); + logger.debug("credentials: {}", credentials); + } catch (final RequiresHttpAction e) { + logger.debug("requires http action: {}", e); + response.flushBuffer(); + final ExternalContext externalContext = ExternalContextHolder.getExternalContext(); + externalContext.recordResponseComplete(); + return new Event(this, "stop"); + } + + // retrieve parameters from web session + final Service service = (Service) session.getAttribute(SERVICE); + context.getFlowScope().put(SERVICE, service); + logger.debug("retrieve service: {}", service); + if (service != null) { + request.setAttribute(SERVICE, service.getId()); + } + restoreRequestAttribute(request, session, THEME); + restoreRequestAttribute(request, session, LOCALE); + restoreRequestAttribute(request, session, METHOD); + + // credentials not null -> try to authenticate + if (credentials != null) { + final TicketGrantingTicket tgt = + this.centralAuthenticationService.createTicketGrantingTicket(new ClientCredential(credentials)); + WebUtils.putTicketGrantingTicketInScopes(context, tgt); + return success(); + } + } + + // no or aborted authentication : go to login page + prepareForLoginPage(context); + return error(); + } + + /** + * Prepare the data for the login page. + * + * @param context The current webflow context + */ + protected void prepareForLoginPage(final RequestContext context) { + final HttpServletRequest request = WebUtils.getHttpServletRequest(context); + final HttpServletResponse response = WebUtils.getHttpServletResponse(context); + final HttpSession session = request.getSession(); + + // web context + final WebContext webContext = new J2EContext(request, response); + + // save parameters in web session + final WebApplicationService service = WebUtils.getService(context); + logger.debug("save service: {}", service); + session.setAttribute(SERVICE, service); + saveRequestParameter(request, session, THEME); + saveRequestParameter(request, session, LOCALE); + saveRequestParameter(request, session, METHOD); + + // for all clients, generate redirection urls + for (final Client client : this.clients.findAllClients()) { + final String key = client.getName() + "Url"; + final BaseClient baseClient = (BaseClient) client; + final String redirectionUrl = baseClient.getRedirectionUrl(webContext); + logger.debug("{} -> {}", key, redirectionUrl); + context.getFlowScope().put(key, redirectionUrl); + } + } + + /** + * Restore an attribute in web session as an attribute in request. + * + * @param request The HTTP request + * @param session The HTTP session + * @param name The name of the parameter + */ + private void restoreRequestAttribute(final HttpServletRequest request, final HttpSession session, + final String name) { + final String value = (String) session.getAttribute(name); + request.setAttribute(name, value); + } + + /** + * Save a request parameter in the web session. + * + * @param request The HTTP request + * @param session The HTTP session + * @param name The name of the parameter + */ + private void saveRequestParameter(final HttpServletRequest request, final HttpSession session, + final String name) { + final String value = request.getParameter(name); + if (value != null) { + session.setAttribute(name, value); + } + } +} diff --git a/cas-server-support-pac4j/src/site/site.xml b/cas-server-support-pac4j/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-support-pac4j/src/site/site.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/cas-server-support-pac4j/src/test/java/org/jasig/cas/support/pac4j/authentication/handler/support/ClientAuthenticationHandlerTests.java b/cas-server-support-pac4j/src/test/java/org/jasig/cas/support/pac4j/authentication/handler/support/ClientAuthenticationHandlerTests.java new file mode 100644 index 000000000000..e1166d9036d4 --- /dev/null +++ b/cas-server-support-pac4j/src/test/java/org/jasig/cas/support/pac4j/authentication/handler/support/ClientAuthenticationHandlerTests.java @@ -0,0 +1,95 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.pac4j.authentication.handler.support; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +import java.security.GeneralSecurityException; + +import javax.security.auth.login.FailedLoginException; + +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.support.pac4j.authentication.principal.ClientCredential; +import org.jasig.cas.support.pac4j.test.MockFacebookClient; +import org.junit.Before; +import org.junit.Test; +import org.pac4j.core.client.Clients; +import org.pac4j.core.credentials.Credentials; +import org.pac4j.oauth.credentials.OAuthCredentials; +import org.pac4j.oauth.profile.facebook.FacebookProfile; +import org.springframework.webflow.context.ExternalContextHolder; +import org.springframework.webflow.context.servlet.ServletExternalContext; + +/** + * Tests the {@link ClientAuthenticationHandler}. + * + * @author Jerome Leleu + * @since 4.1.0 + * + */ +public final class ClientAuthenticationHandlerTests { + + private static final String CALLBACK_URL = "http://localhost:8080/callback"; + private static final String ID = "123456789"; + + private MockFacebookClient fbClient; + + private ClientAuthenticationHandler handler; + + private ClientCredential clientCredential; + + @Before + public void setUp() { + this.fbClient = new MockFacebookClient(); + final Clients clients = new Clients(CALLBACK_URL, fbClient); + this.handler = new ClientAuthenticationHandler(clients); + final Credentials credentials = new OAuthCredentials(null, MockFacebookClient.CLIENT_NAME); + this.clientCredential = new ClientCredential(credentials); + ExternalContextHolder.setExternalContext(mock(ServletExternalContext.class)); + } + + @Test + public void verifyOk() throws GeneralSecurityException, PreventedException { + final FacebookProfile facebookProfile = new FacebookProfile(); + facebookProfile.setId(ID); + this.fbClient.setFacebookProfile(facebookProfile); + final HandlerResult result = this.handler.authenticate(this.clientCredential); + final Principal principal = result.getPrincipal(); + assertEquals(FacebookProfile.class.getSimpleName() + "#" + ID, principal.getId()); + } + + @Test + public void verifyOkWithSimpleIdentifier() throws GeneralSecurityException, PreventedException { + this.handler.setTypedIdUsed(false); + final FacebookProfile facebookProfile = new FacebookProfile(); + facebookProfile.setId(ID); + this.fbClient.setFacebookProfile(facebookProfile); + final HandlerResult result = this.handler.authenticate(this.clientCredential); + final Principal principal = result.getPrincipal(); + assertEquals(ID, principal.getId()); + } + + @Test(expected = FailedLoginException.class) + public void verifyNoProfile() throws GeneralSecurityException, PreventedException { + this.handler.authenticate(this.clientCredential); + } +} diff --git a/cas-server-support-pac4j/src/test/java/org/jasig/cas/support/pac4j/test/MockFacebookClient.java b/cas-server-support-pac4j/src/test/java/org/jasig/cas/support/pac4j/test/MockFacebookClient.java new file mode 100644 index 000000000000..3f796f9587b7 --- /dev/null +++ b/cas-server-support-pac4j/src/test/java/org/jasig/cas/support/pac4j/test/MockFacebookClient.java @@ -0,0 +1,64 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.pac4j.test; + +import org.pac4j.core.context.WebContext; +import org.pac4j.oauth.client.FacebookClient; +import org.pac4j.oauth.credentials.OAuthCredentials; +import org.pac4j.oauth.profile.facebook.FacebookProfile; + +/** + * Mock class for the FacebookClient. + * + * @author Jerome Leleu + * @since 4.0.0 + */ +public class MockFacebookClient extends FacebookClient { + + public static final String CLIENT_NAME = "FacebookClient"; + + private FacebookProfile facebookProfile; + + @Override + public String getName() { + return CLIENT_NAME; + } + + @Override + protected void internalInit() { + } + + @Override + protected OAuthCredentials retrieveCredentials(final WebContext context) { + return new OAuthCredentials("fakeVerifier", getName()); + } + + @Override + protected FacebookProfile retrieveUserProfile(final OAuthCredentials credentials, final WebContext context) { + return facebookProfile; + } + + public FacebookProfile getFacebookProfile() { + return facebookProfile; + } + + public void setFacebookProfile(final FacebookProfile facebookProfile) { + this.facebookProfile = facebookProfile; + } +} diff --git a/cas-server-support-pac4j/src/test/java/org/jasig/cas/support/pac4j/web/flow/ClientActionTests.java b/cas-server-support-pac4j/src/test/java/org/jasig/cas/support/pac4j/web/flow/ClientActionTests.java new file mode 100644 index 000000000000..47eedbf24cfd --- /dev/null +++ b/cas-server-support-pac4j/src/test/java/org/jasig/cas/support/pac4j/web/flow/ClientActionTests.java @@ -0,0 +1,172 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.pac4j.web.flow; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Matchers.any; + +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.support.pac4j.test.MockFacebookClient; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.junit.Test; +import org.pac4j.core.client.Clients; +import org.pac4j.core.exception.TechnicalException; +import org.pac4j.http.client.BasicAuthClient; +import org.pac4j.oauth.client.FacebookClient; +import org.pac4j.oauth.client.TwitterClient; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.core.collection.MutableAttributeMap; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.test.MockRequestContext; + +/** + * This class tests the {@link ClientAction} class. + * + * @author Jerome Leleu + * @since 3.5.2 + */ +@SuppressWarnings("rawtypes") +public final class ClientActionTests { + + private static final String TGT_NAME = "ticketGrantingTicketId"; + private static final String TGT_ID = "TGT-00-xxxxxxxxxxxxxxxxxxxxxxxxxx.cas0"; + + private static final String MY_KEY = "my_key"; + + private static final String MY_SECRET = "my_secret"; + + private static final String MY_LOGIN_URL = "http://casserver/login"; + + private static final String MY_SERVICE = "http://myservice"; + + private static final String MY_THEME = "my_theme"; + + private static final String MY_LOCALE = "fr"; + + private static final String MY_METHOD = "POST"; + + @Test + public void verifyStartAuthentication() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + mockRequest.setParameter(ClientAction.THEME, MY_THEME); + mockRequest.setParameter(ClientAction.LOCALE, MY_LOCALE); + mockRequest.setParameter(ClientAction.METHOD, MY_METHOD); + + final MockHttpSession mockSession = new MockHttpSession(); + mockRequest.setSession(mockSession); + + final ServletExternalContext servletExternalContext = mock(ServletExternalContext.class); + when(servletExternalContext.getNativeRequest()).thenReturn(mockRequest); + + final MockRequestContext mockRequestContext = new MockRequestContext(); + mockRequestContext.setExternalContext(servletExternalContext); + mockRequestContext.getFlowScope().put(ClientAction.SERVICE, new SimpleWebApplicationServiceImpl(MY_SERVICE)); + + final FacebookClient facebookClient = new FacebookClient(MY_KEY, MY_SECRET); + final TwitterClient twitterClient = new TwitterClient(MY_KEY, MY_SECRET); + final Clients clients = new Clients(MY_LOGIN_URL, facebookClient, twitterClient); + final ClientAction action = new ClientAction(mock(CentralAuthenticationService.class), clients); + + final Event event = action.execute(mockRequestContext); + assertEquals("error", event.getId()); + assertEquals(MY_THEME, mockSession.getAttribute(ClientAction.THEME)); + assertEquals(MY_LOCALE, mockSession.getAttribute(ClientAction.LOCALE)); + assertEquals(MY_METHOD, mockSession.getAttribute(ClientAction.METHOD)); + final MutableAttributeMap flowScope = mockRequestContext.getFlowScope(); + assertTrue(((String) flowScope.get("FacebookClientUrl")) + .startsWith("https://www.facebook.com/v2.2/dialog/oauth?client_id=my_key&redirect_uri=http%3A%2F%2Fcasserver%2Flogin%3F" + + Clients.DEFAULT_CLIENT_NAME_PARAMETER + "%3DFacebookClient&state=")); + assertEquals(MY_LOGIN_URL + "?" + Clients.DEFAULT_CLIENT_NAME_PARAMETER + + "=TwitterClient&needs_client_redirection=true", flowScope.get("TwitterClientUrl")); + } + + @Test + public void verifyFinishAuthentication() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + mockRequest.setParameter(Clients.DEFAULT_CLIENT_NAME_PARAMETER, "FacebookClient"); + + final MockHttpSession mockSession = new MockHttpSession(); + mockSession.setAttribute(ClientAction.THEME, MY_THEME); + mockSession.setAttribute(ClientAction.LOCALE, MY_LOCALE); + mockSession.setAttribute(ClientAction.METHOD, MY_METHOD); + final Service service = new SimpleWebApplicationServiceImpl(MY_SERVICE); + mockSession.setAttribute(ClientAction.SERVICE, service); + mockRequest.setSession(mockSession); + + final ServletExternalContext servletExternalContext = mock(ServletExternalContext.class); + when(servletExternalContext.getNativeRequest()).thenReturn(mockRequest); + + final MockRequestContext mockRequestContext = new MockRequestContext(); + mockRequestContext.setExternalContext(servletExternalContext); + + final FacebookClient facebookClient = new MockFacebookClient(); + final Clients clients = new Clients(MY_LOGIN_URL, facebookClient); + + final TicketGrantingTicket tgt = new TicketGrantingTicketImpl(TGT_ID, mock(Authentication.class), mock(ExpirationPolicy.class)); + final CentralAuthenticationService casImpl = mock(CentralAuthenticationService.class); + when(casImpl.createTicketGrantingTicket(any(Credential.class))).thenReturn(tgt); + final ClientAction action = new ClientAction(casImpl, clients); + final Event event = action.execute(mockRequestContext); + assertEquals("success", event.getId()); + assertEquals(MY_THEME, mockRequest.getAttribute(ClientAction.THEME)); + assertEquals(MY_LOCALE, mockRequest.getAttribute(ClientAction.LOCALE)); + assertEquals(MY_METHOD, mockRequest.getAttribute(ClientAction.METHOD)); + assertEquals(MY_SERVICE, mockRequest.getAttribute(ClientAction.SERVICE)); + final MutableAttributeMap flowScope = mockRequestContext.getFlowScope(); + final MutableAttributeMap requestScope = mockRequestContext.getRequestScope(); + assertEquals(service, flowScope.get(ClientAction.SERVICE)); + assertEquals(TGT_ID, flowScope.get(TGT_NAME)); + assertEquals(TGT_ID, requestScope.get(TGT_NAME)); + } + + @Test + public void checkUnautorizedProtocol() throws Exception { + final MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + mockRequest.setParameter(Clients.DEFAULT_CLIENT_NAME_PARAMETER, "BasicAuthClient"); + + final ServletExternalContext servletExternalContext = mock(ServletExternalContext.class); + when(servletExternalContext.getNativeRequest()).thenReturn(mockRequest); + + final MockRequestContext mockRequestContext = new MockRequestContext(); + mockRequestContext.setExternalContext(servletExternalContext); + + final BasicAuthClient basicAuthClient = new BasicAuthClient(); + final Clients clients = new Clients(MY_LOGIN_URL, basicAuthClient); + final ClientAction action = new ClientAction(mock(CentralAuthenticationService.class), clients); + + try { + action.execute(mockRequestContext); + fail("Should fail as the HTTP protocol is not authorized"); + } catch (final TechnicalException e) { + assertEquals("Only CAS, OAuth, OpenID and SAML protocols are supported: " + basicAuthClient, e.getMessage()); + } + } +} diff --git a/cas-server-support-pac4j/src/test/resources/log4j2.xml b/cas-server-support-pac4j/src/test/resources/log4j2.xml new file mode 100644 index 000000000000..0d9099c9b8cb --- /dev/null +++ b/cas-server-support-pac4j/src/test/resources/log4j2.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-3.4.2/cas-server-support-ldap/.cvsignore b/cas-server-support-radius/.cvsignore similarity index 100% rename from cas-server-3.4.2/cas-server-support-ldap/.cvsignore rename to cas-server-support-radius/.cvsignore diff --git a/cas-server-support-radius/NOTICE b/cas-server-support-radius/NOTICE new file mode 100644 index 000000000000..df8b91ba2e76 --- /dev/null +++ b/cas-server-support-radius/NOTICE @@ -0,0 +1,117 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS RADIUS Support under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + CDI APIs under Apache License, Version 2.0 + Commons Chain under The Apache Software License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons Configuration under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Commons Pool under The Apache Software License, Version 2.0 + commons-beanutils under Apache License, Version 2.0 + commons-beanutils-core under Apache License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + Digester under The Apache Software License, Version 2.0 + dom4j under BSD License + ehcache under The Apache Software License, Version 2.0 or Terracotta Public License + Ehcache Core under The Apache Software License, Version 2.0 + ehcache-terracotta under Terracotta Public License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + gnu-crypto under GNU General Public License, with the "library exception" + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + java-getopt under GNU General Public License, with the "library exception" + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + jradius-core-1.1.3 under GNU Lessor/Library Public License, Version 3.0 or GNU Public License, Version 3.0 + jradius-dictionary-1.1.3 under GNU Lessor/Library Public License, Version 3.0 or GNU Public License, Version 3.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Lang under The Apache Software License, Version 2.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + SLF4J LOG4J-12 Binding under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + terracotta-toolkit-runtime under Terracotta Public License + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-support-radius/pom.xml b/cas-server-support-radius/pom.xml new file mode 100644 index 000000000000..ef8ef4be4c87 --- /dev/null +++ b/cas-server-support-radius/pom.xml @@ -0,0 +1,82 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-support-radius + jar + Apereo CAS RADIUS Support + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + net.jradius + jradius-core + ${radius.version} + + + commons-logging + commons-logging + + + log4j + log4j + + + + + + net.jradius + jradius-dictionary + ${radius.version} + + + + + + + + coova + Coova Repository + http://coova-dev.s3.amazonaws.com/mvn + + + + + ${project.parent.basedir} + 1.1.3 + + diff --git a/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/JRadiusServerImpl.java b/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/JRadiusServerImpl.java new file mode 100644 index 000000000000..1146ef48dc8e --- /dev/null +++ b/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/JRadiusServerImpl.java @@ -0,0 +1,251 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.radius; + + +import net.jradius.client.RadiusClient; +import net.jradius.dictionary.Attr_NASIPAddress; +import net.jradius.dictionary.Attr_NASIPv6Address; +import net.jradius.dictionary.Attr_NASIdentifier; +import net.jradius.dictionary.Attr_NASPort; +import net.jradius.dictionary.Attr_NASPortId; +import net.jradius.dictionary.Attr_NASPortType; +import net.jradius.dictionary.Attr_UserName; +import net.jradius.dictionary.Attr_UserPassword; +import net.jradius.dictionary.vsa_redback.Attr_NASRealPort; +import net.jradius.packet.AccessAccept; +import net.jradius.packet.AccessRequest; +import net.jradius.packet.RadiusPacket; +import net.jradius.packet.attribute.AttributeFactory; +import net.jradius.packet.attribute.AttributeList; +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.authentication.PreventedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; + +/** + * Implementation of a RadiusServer that utilizes the JRadius packages available + * at http://jradius.sf.net. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @author Misagh Moayyed + * @since 3.1 + */ +public final class JRadiusServerImpl implements RadiusServer { + + /** + * Default retry count, {@value}. + **/ + public static final int DEFAULT_RETRY_COUNT = 3; + + /** Logger instance. */ + private static final Logger LOGGER = LoggerFactory.getLogger(JRadiusServerImpl.class); + + /** RADIUS protocol. */ + @NotNull + private final RadiusProtocol protocol; + + /** Produces RADIUS client instances for authentication. */ + @NotNull + private final RadiusClientFactory radiusClientFactory; + + /** Number of times to retry authentication when no response is received. */ + @Min(0) + private int retries = DEFAULT_RETRY_COUNT; + + private String nasIpAddress; + + private String nasIpv6Address; + + private long nasPort = -1; + + private long nasPortId = -1; + + private long nasIdentifier = -1; + + private long nasRealPort = -1; + + private long nasPortType = -1; + + + /** Load the dictionary implementation. */ + static { + AttributeFactory + .loadAttributeDictionary("net.jradius.dictionary.AttributeDictionaryImpl"); + } + + /** + * Instantiates a new server implementation + * with the radius protocol and client factory specified. + * + * @param protocol the protocol + * @param clientFactory the client factory + */ + public JRadiusServerImpl(final RadiusProtocol protocol, final RadiusClientFactory clientFactory) { + this.protocol = protocol; + this.radiusClientFactory = clientFactory; + } + + @Override + public RadiusResponse authenticate(final String username, final String password) throws PreventedException { + + final AttributeList attributeList = new AttributeList(); + + attributeList.add(new Attr_UserName(username)); + attributeList.add(new Attr_UserPassword(password)); + + if (StringUtils.isNotBlank(this.nasIpAddress)) { + attributeList.add(new Attr_NASIPAddress(this.nasIpAddress)); + } + if (StringUtils.isNotBlank(this.nasIpv6Address)) { + attributeList.add(new Attr_NASIPv6Address(this.nasIpv6Address)); + } + + if (this.nasPort != -1) { + attributeList.add(new Attr_NASPort(this.nasPort)); + } + if (this.nasPortId != -1) { + attributeList.add(new Attr_NASPortId(this.nasPortId)); + } + if (this.nasIdentifier != -1) { + attributeList.add(new Attr_NASIdentifier(this.nasIdentifier)); + } + if (this.nasRealPort != -1) { + attributeList.add(new Attr_NASRealPort(this.nasRealPort)); + } + if (this.nasPortType != -1) { + attributeList.add(new Attr_NASPortType(this.nasPortType)); + } + + RadiusClient client = null; + try { + client = this.radiusClientFactory.newInstance(); + final AccessRequest request = new AccessRequest(client, attributeList); + final RadiusPacket response = client.authenticate( + request, + RadiusClient.getAuthProtocol(this.protocol.getName()), + this.retries); + + LOGGER.debug("RADIUS response from {}: {}", + client.getRemoteInetAddress().getCanonicalHostName(), + response.getClass().getName()); + + if (response instanceof AccessAccept) { + final AccessAccept acceptedResponse = (AccessAccept) response; + + return new RadiusResponse(acceptedResponse.getCode(), + acceptedResponse.getIdentifier(), + acceptedResponse.getAttributes().getAttributeList()); + } + } catch (final Exception e) { + throw new PreventedException(e); + } finally { + if (client != null) { + client.close(); + } + } + return null; + } + + + /** + * Sets the nas ip address. + * + * @param nasIpAddress the new nas ip address + * @since 4.1.0 + */ + public void setNasIpAddress(final String nasIpAddress) { + this.nasIpAddress = nasIpAddress; + } + + /** + * Sets the nas ipv6 address. + * + * @param nasIpv6Address the new nas ipv6 address + * @since 4.1.0 + */ + public void setNasIpv6Address(final String nasIpv6Address) { + this.nasIpv6Address = nasIpv6Address; + } + + /** + * Sets the nas port. + * + * @param nasPort the new nas port + * @since 4.1.0 + */ + public void setNasPort(final long nasPort) { + this.nasPort = nasPort; + } + + /** + * Sets the nas port id. + * + * @param nasPortId the new nas port id + * @since 4.1.0 + */ + public void setNasPortId(final long nasPortId) { + this.nasPortId = nasPortId; + } + + /** + * Sets the nas identifier. + * + * @param nasIdentifier the new nas identifier + * @since 4.1.0 + */ + public void setNasIdentifier(final long nasIdentifier) { + this.nasIdentifier = nasIdentifier; + } + + /** + * Sets the nas real port. + * + * @param nasRealPort the new nas real port + * @since 4.1.0 + */ + public void setNasRealPort(final long nasRealPort) { + this.nasRealPort = nasRealPort; + } + + /** + * Sets the nas port type. + * + * @param nasPortType the new nas port type + * @since 4.1.0 + */ + public void setNasPortType(final long nasPortType) { + this.nasPortType = nasPortType; + } + + /** + * Sets the retries. + * + * @param retries the new retries + * @since 4.1.0 + */ + public void setRetries(final int retries) { + this.retries = retries; + } + +} diff --git a/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/RadiusClientFactory.java b/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/RadiusClientFactory.java new file mode 100644 index 000000000000..c891005297f6 --- /dev/null +++ b/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/RadiusClientFactory.java @@ -0,0 +1,118 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.radius; + +import net.jradius.client.RadiusClient; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * Factory for creating RADIUS client instances. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class RadiusClientFactory { + + private static final int DEFAULT_SOCKET_TIMEOUT = 60; + + /** The port to do accounting on. */ + @Min(1) + private int accountingPort = RadiusServer.DEFAULT_PORT_ACCOUNTING; + + /** The port to do authentication on. */ + @Min(1) + private int authenticationPort = RadiusServer.DEFAULT_PORT_AUTHENTICATION; + + /** Socket timeout in seconds. */ + @Min(0) + private int socketTimeout = DEFAULT_SOCKET_TIMEOUT; + + /** RADIUS server network address. */ + @NotNull + private InetAddress inetAddress; + + /** The shared secret to send to the RADIUS server. */ + @NotNull + private String sharedSecret; + + /** + * Sets the RADIUS server accounting port. + * + * @param port Accounting port number. + */ + public void setAccountingPort(final int port) { + this.accountingPort = port; + } + + /** + * Sets the RADIUS server authentication port. + * + * @param port Authentication port number. + */ + public void setAuthenticationPort(final int port) { + this.authenticationPort = port; + } + + /** + * Sets the RADIUS server UDP socket timeout. + * + * @param timeout Timeout in seconds; 0 for no timeout. + */ + public void setSocketTimeout(final int timeout) { + this.socketTimeout = timeout; + } + + /** + * RADIUS server network address. + * + * @param address Network address as a string. + */ + public void setInetAddress(final String address) { + try { + this.inetAddress = InetAddress.getByName(address); + } catch (final UnknownHostException e) { + throw new RuntimeException("Invalid address " + address); + } + } + + /** + * RADIUS server authentication shared secret. + * + * @param secret Shared secret. + */ + public void setSharedSecret(final String secret) { + this.sharedSecret = secret; + } + + /** + * Creates a new RADIUS client instance using factory configuration settings. + * + * @return New radius client instance. + * @throws IOException In case the transport method encounters an error. + */ + public RadiusClient newInstance() throws IOException { + return new RadiusClient( + this.inetAddress, this.sharedSecret, this.authenticationPort, this.accountingPort, this.socketTimeout); + } +} diff --git a/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/RadiusProtocol.java b/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/RadiusProtocol.java new file mode 100644 index 000000000000..11cca118fbca --- /dev/null +++ b/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/RadiusProtocol.java @@ -0,0 +1,82 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.radius; + +/** + * RADIUS protocol enumeration. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public enum RadiusProtocol { + + /** The chap. */ + CHAP("chap"), + + /** The EA p_ m d5. */ + EAP_MD5("eap-md5"), + + /** The EA p_ mscha pv2. */ + EAP_MSCHAPv2("eap-mschapv2"), + + /** The eap tls. */ + EAP_TLS("eap-tls"), + + /** The eap ttls pap. */ + EAP_TTLS_PAP("eap-ttls:innerProtocol=pap"), + + /** The EA p_ ttl s_ ea p_ m d5. */ + EAP_TTLS_EAP_MD5("eap-ttls:innerProtocol=eap-md5"), + + /** The EA p_ ttl s_ ea p_ mscha pv2. */ + EAP_TTLS_EAP_MSCHAPv2("eap-ttls:innerProtocol=eap-mschapv2"), + + /** The MSCHA pv1. */ + MSCHAPv1("mschapv1"), + + /** The MSCHA pv2. */ + MSCHAPv2("mschapv2"), + + /** The pap. */ + PAP("pap"), + + /** The peap. */ + PEAP("peap"); + + /** The name. */ + private final String name; + + /** + * Instantiates a new radius protocol. + * + * @param name the name + */ + RadiusProtocol(final String name) { + this.name = name; + } + + /** + * Gets the radius protocol name required by {@link net.jradius.client.RadiusClient#getAuthProtocol(String)}. + * + * @return RADIUS protocol name known to {@link net.jradius.client.RadiusClient}. + */ + public String getName() { + return this.name; + } +} diff --git a/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/RadiusResponse.java b/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/RadiusResponse.java new file mode 100644 index 000000000000..eb07d376152c --- /dev/null +++ b/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/RadiusResponse.java @@ -0,0 +1,68 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.radius; + +import java.util.List; + +import net.jradius.packet.attribute.RadiusAttribute; + +/** + * Acts as a DTO, to carry the response returned by the + * Radius authenticator in the event of a successful authentication, + * and provides access to the response code as well as attributes + * which may be used as authentication attributes. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class RadiusResponse { + + /** The code. */ + private final int code; + + /** The identifier. */ + private final int identifier; + + /** The attributes. */ + private final List attributes; + + /** + * Instantiates a new radius response. + * + * @param code the code + * @param identifier the identifier + * @param attributes the attributes + */ + public RadiusResponse(final int code, final int identifier, final List attributes) { + this.code = code; + this.identifier = identifier; + this.attributes = attributes; + } + + public int getCode() { + return this.code; + } + + public int getIdentifier() { + return this.identifier; + } + + public List getAttributes() { + return this.attributes; + } +} diff --git a/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/RadiusServer.java b/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/RadiusServer.java new file mode 100644 index 000000000000..884439de74d4 --- /dev/null +++ b/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/RadiusServer.java @@ -0,0 +1,54 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.radius; + +import org.jasig.cas.authentication.PreventedException; + +/** + * Interface representing a Radius Server. + * + * @author Scott Battaglia + * @author Misagh Moayyed + * @since 3.1 + */ +public interface RadiusServer { + + /** The default port for accounting. + * @since 4.1.0 + **/ + int DEFAULT_PORT_ACCOUNTING = 1813; + + /** The default port for authentication. + * @since 4.1.0 + **/ + int DEFAULT_PORT_AUTHENTICATION = 1812; + + /** + * Method to authenticate a set of credentials. + * + * @param username Non-null username to authenticate. + * @param password Password to authenticate. + * + * @return {@link RadiusResponse} on success, null otherwise. + * + * @throws PreventedException On indeterminate case where authentication was prevented by a system (e.g. IO) error. + */ + RadiusResponse authenticate(String username, String password) throws PreventedException; + +} diff --git a/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/authentication/handler/support/RadiusAuthenticationHandler.java b/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/authentication/handler/support/RadiusAuthenticationHandler.java new file mode 100644 index 000000000000..32edb107e11e --- /dev/null +++ b/cas-server-support-radius/src/main/java/org/jasig/cas/adaptors/radius/authentication/handler/support/RadiusAuthenticationHandler.java @@ -0,0 +1,113 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.radius.authentication.handler.support; + +import org.jasig.cas.adaptors.radius.RadiusResponse; +import org.jasig.cas.adaptors.radius.RadiusServer; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler; + +import javax.security.auth.login.FailedLoginException; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.security.GeneralSecurityException; +import java.util.List; + +/** + * Authentication Handler to authenticate a user against a RADIUS server. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public class RadiusAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler { + + /** Array of RADIUS servers to authenticate against. */ + @NotNull + @Size(min=1) + private List servers; + + /** + * Determines whether to fail over to the next configured RadiusServer if + * there was an exception. + */ + private boolean failoverOnException; + + /** + * Determines whether to fail over to the next configured RadiusServer if + * there was an authentication failure. + */ + private boolean failoverOnAuthenticationFailure; + + @Override + protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential) + throws GeneralSecurityException, PreventedException { + + final String password = getPasswordEncoder().encode(credential.getPassword()); + final String username = credential.getUsername(); + + for (final RadiusServer radiusServer : this.servers) { + logger.debug("Attempting to authenticate {} at {}", username, radiusServer); + try { + final RadiusResponse response = radiusServer.authenticate(username, password); + if (response != null) { + return createHandlerResult(credential, this.principalFactory.createPrincipal(username), null); + } + + if (!this.failoverOnAuthenticationFailure) { + throw new FailedLoginException("Radius authentication failed for user " + username); + } + logger.debug("failoverOnAuthenticationFailure enabled -- trying next server"); + } catch (final PreventedException e) { + if (!this.failoverOnException) { + throw e; + } + logger.warn("failoverOnException enabled -- trying next server.", e); + } + } + throw new FailedLoginException("Radius authentication failed for user " + username); + } + + /** + * Determines whether to fail over to the next configured RadiusServer if + * there was an authentication failure. + * + * @param failoverOnAuthenticationFailure boolean on whether to failover or + * not. + */ + public final void setFailoverOnAuthenticationFailure( + final boolean failoverOnAuthenticationFailure) { + this.failoverOnAuthenticationFailure = failoverOnAuthenticationFailure; + } + + /** + * Determines whether to fail over to the next configured RadiusServer if + * there was an exception. + * + * @param failoverOnException boolean on whether to failover or not. + */ + public final void setFailoverOnException(final boolean failoverOnException) { + this.failoverOnException = failoverOnException; + } + + public final void setServers(final List servers) { + this.servers = servers; + } +} diff --git a/cas-server-support-radius/src/site/site.xml b/cas-server-support-radius/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-support-radius/src/site/site.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/cas-server-3.4.2/cas-server-support-legacy/src/test/clover/clover.license b/cas-server-support-radius/src/test/clover/clover.license similarity index 100% rename from cas-server-3.4.2/cas-server-support-legacy/src/test/clover/clover.license rename to cas-server-support-radius/src/test/clover/clover.license diff --git a/cas-server-support-radius/src/test/java/org/jasig/cas/adaptors/radius/JRadiusServerImplTest.java b/cas-server-support-radius/src/test/java/org/jasig/cas/adaptors/radius/JRadiusServerImplTest.java new file mode 100644 index 000000000000..4de173082259 --- /dev/null +++ b/cas-server-support-radius/src/test/java/org/jasig/cas/adaptors/radius/JRadiusServerImplTest.java @@ -0,0 +1,50 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.radius; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.assertNotNull; + +/** + * Unit test for {@link JRadiusServerImpl}. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("/test-context.xml") +public class JRadiusServerImplTest { + + @Autowired + private JRadiusServerImpl radiusServer; + + /** + * Presently this only tests component wiring. + * An external RADIUS server test fixture is required for thorough testing. + */ + @Test + public void verifyAuthenticate() { + assertNotNull(this.radiusServer); + } +} diff --git a/cas-server-support-radius/src/test/resources/test-context.xml b/cas-server-support-radius/src/test/resources/test-context.xml new file mode 100644 index 000000000000..177a8530025d --- /dev/null +++ b/cas-server-support-radius/src/test/resources/test-context.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + diff --git a/cas-server-support-rest/NOTICE b/cas-server-support-rest/NOTICE new file mode 100644 index 000000000000..c16b97419f53 --- /dev/null +++ b/cas-server-support-rest/NOTICE @@ -0,0 +1,101 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS REST Implementation under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + CDI APIs under Apache License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-support-rest/pom.xml b/cas-server-support-rest/pom.xml new file mode 100644 index 000000000000..86028b3578ee --- /dev/null +++ b/cas-server-support-rest/pom.xml @@ -0,0 +1,44 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-support-rest + jar + Apereo CAS REST Implementation + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + + ${project.parent.basedir} + + + diff --git a/cas-server-support-rest/src/main/java/org/jasig/cas/support/rest/TicketsResource.java b/cas-server-support-rest/src/main/java/org/jasig/cas/support/rest/TicketsResource.java new file mode 100644 index 000000000000..a9033a3e8a7b --- /dev/null +++ b/cas-server-support-rest/src/main/java/org/jasig/cas/support/rest/TicketsResource.java @@ -0,0 +1,140 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.rest; + +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.ticket.InvalidTicketException; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import javax.servlet.http.HttpServletRequest; +import java.net.URI; +import java.util.Formatter; + +/** + * {@link org.springframework.web.bind.annotation.RestController} implementation of CAS' REST API. + * + * This class implements main CAS RESTful resource for vending/deleting TGTs and vending STs: + * + *
    + *
  • {@code POST /v1/tickets}
  • + *
  • {@code POST /v1/tickets/{TGT-id}}
  • + *
  • {@code DELETE /v1/tickets/{TGT-id}}
  • + *
+ * + * @author Dmitriy Kopylenko + * @since 4.1.0 + */ +@RestController("/v1") +public class TicketsResource { + + private static final Logger LOGGER = LoggerFactory.getLogger(TicketsResource.class); + + @Autowired + private CentralAuthenticationService cas; + + /** + * Create new ticket granting ticket. + * + * @param requestBody username and password application/x-www-form-urlencoded values + * @param request raw HttpServletRequest used to call this method + * @return ResponseEntity representing RESTful response + */ + @RequestMapping(value = "/tickets", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + public final ResponseEntity createTicketGrantingTicket(@RequestBody final MultiValueMap requestBody, + final HttpServletRequest request) { + try (Formatter fmt = new Formatter()) { + final TicketGrantingTicket tgtId = this.cas.createTicketGrantingTicket(obtainCredential(requestBody)); + final URI ticketReference = new URI(request.getRequestURL().toString() + '/' + tgtId.getId()); + final HttpHeaders headers = new HttpHeaders(); + headers.setLocation(ticketReference); + headers.setContentType(MediaType.TEXT_HTML); + fmt.format(""); + //IETF//DTD HTML 2.0//EN\\\"><html><head><title>"); + fmt.format("%s %s", HttpStatus.CREATED, HttpStatus.CREATED.getReasonPhrase()) + .format("

TGT Created

Service:") + .format("
"); + return new ResponseEntity(fmt.toString(), headers, HttpStatus.CREATED); + } catch (final Throwable e) { + LOGGER.error(e.getMessage(), e); + return new ResponseEntity(e.getMessage(), HttpStatus.BAD_REQUEST); + } + } + + /** + * Create new service ticket. + * + * @param requestBody service application/x-www-form-urlencoded value + * @param tgtId ticket granting ticket id URI path param + * @return {@link ResponseEntity} representing RESTful response + */ + @RequestMapping(value = "/tickets/{tgtId:.+}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + public final ResponseEntity createServiceTicket(@RequestBody final MultiValueMap requestBody, + @PathVariable("tgtId") final String tgtId) { + try { + final ServiceTicket serviceTicketId = this.cas.grantServiceTicket(tgtId, + new SimpleWebApplicationServiceImpl(requestBody.getFirst("service"))); + return new ResponseEntity(serviceTicketId.getId(), HttpStatus.OK); + } catch (final InvalidTicketException e) { + return new ResponseEntity("TicketGrantingTicket could not be found", HttpStatus.NOT_FOUND); + } catch (final Exception e) { + LOGGER.error(e.getMessage(), e); + return new ResponseEntity(e.getMessage(), HttpStatus.BAD_REQUEST); + } + } + + /** + * Destroy ticket granting ticket. + * + * @param tgtId ticket granting ticket id URI path param + * @return {@link ResponseEntity} representing RESTful response. Signals + * {@link HttpStatus#OK} when successful. + */ + @RequestMapping(value = "/tickets/{tgtId:.+}", method = RequestMethod.DELETE) + public final ResponseEntity deleteTicketGrantingTicket(@PathVariable("tgtId") final String tgtId) { + this.cas.destroyTicketGrantingTicket(tgtId); + return new ResponseEntity(tgtId, HttpStatus.OK); + } + + /** + * Obtain credential from the request. Could be overridden by subclasses. + * + * @param requestBody raw entity request body + * @return the credential instance + */ + protected Credential obtainCredential(final MultiValueMap requestBody) { + return new UsernamePasswordCredential(requestBody.getFirst("username"), requestBody.getFirst("password")); + } +} diff --git a/cas-server-support-rest/src/main/resources/META-INF/spring/rest-context.xml b/cas-server-support-rest/src/main/resources/META-INF/spring/rest-context.xml new file mode 100644 index 000000000000..63bb0491b393 --- /dev/null +++ b/cas-server-support-rest/src/main/resources/META-INF/spring/rest-context.xml @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/cas-server-support-rest/src/site/site.xml b/cas-server-support-rest/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-support-rest/src/site/site.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/cas-server-support-rest/src/test/java/org/jasig/cas/support/rest/TicketsResourceTests.java b/cas-server-support-rest/src/test/java/org/jasig/cas/support/rest/TicketsResourceTests.java new file mode 100644 index 000000000000..fa8532268371 --- /dev/null +++ b/cas-server-support-rest/src/test/java/org/jasig/cas/support/rest/TicketsResourceTests.java @@ -0,0 +1,161 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.rest; + +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.AuthenticationException; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.ticket.InvalidTicketException; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import javax.security.auth.login.LoginException; +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Unit tests for {@link org.jasig.cas.support.rest.TicketsResource}. + * + * @author Dmitriy Kopylenko + * @since 4.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class TicketsResourceTests { + + @Mock + private CentralAuthenticationService casMock; + + @InjectMocks + private TicketsResource ticketsResourceUnderTest; + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = MockMvcBuilders.standaloneSetup(this.ticketsResourceUnderTest) + .defaultRequest(get("/") + .contextPath("/cas") + .servletPath("/v1") + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .build(); + } + + @Test + public void normalCreationOfTGT() throws Throwable { + final String expectedReturnEntityBody = "" + + "201 Created

TGT Created

" + + "
Service:" + + "
"; + + configureCasMockToCreateValidTGT(); + + this.mockMvc.perform(post("/cas/v1/tickets") + .param("username", "test") + .param("password", "test")) + .andExpect(status().isCreated()) + .andExpect(header().string("Location", "http://localhost/cas/v1/tickets/TGT-1")) + .andExpect(content().contentType(MediaType.TEXT_HTML)) + .andExpect(content().string(expectedReturnEntityBody)); + } + + @Test + public void creationOfTGTWithAuthenticationException() throws Throwable { + configureCasMockTGTCreationToThrowAuthenticationException(); + + this.mockMvc.perform(post("/cas/v1/tickets") + .param("username", "test") + .param("password", "test")) + .andExpect(status().is4xxClientError()) + .andExpect(content().string("1 errors, 0 successes")); + } + + @Test + public void normalCreationOfST() throws Throwable { + configureCasMockToCreateValidST(); + + this.mockMvc.perform(post("/cas/v1/tickets/TGT-1") + .param("service", "https://www.google.com")) + .andExpect(status().isOk()) + .andExpect(content().contentType("text/plain;charset=ISO-8859-1")) + .andExpect(content().string("ST-1")); + } + + @Test + public void creationOfSTWithInvalidTicketException() throws Throwable { + configureCasMockSTCreationToThrow(new InvalidTicketException("TGT-1")); + + this.mockMvc.perform(post("/cas/v1/tickets/TGT-1") + .param("service", "https://www.google.com")) + .andExpect(status().isNotFound()) + .andExpect(content().string("TicketGrantingTicket could not be found")); + } + + @Test + public void creationOfSTWithGeneralException() throws Throwable { + configureCasMockSTCreationToThrow(new RuntimeException("Other exception")); + + this.mockMvc.perform(post("/cas/v1/tickets/TGT-1") + .param("service", "https://www.google.com")) + .andExpect(status().is4xxClientError()) + .andExpect(content().string("Other exception")); + } + + @Test + public void deletionOfTGT() throws Throwable { + this.mockMvc.perform(delete("/cas/v1/tickets/TGT-1")) + .andExpect(status().isOk()); + } + + private void configureCasMockToCreateValidTGT() throws Throwable { + final TicketGrantingTicket tgt = mock(TicketGrantingTicket.class); + when(tgt.getId()).thenReturn("TGT-1"); + when(this.casMock.createTicketGrantingTicket(any(Credential.class))).thenReturn(tgt); + } + + private void configureCasMockTGTCreationToThrowAuthenticationException() throws Throwable { + final Map> handlerErrors = new HashMap<>(1); + handlerErrors.put("TestCaseAuthenticationHander", LoginException.class); + when(this.casMock.createTicketGrantingTicket(any(Credential.class))).thenThrow(new AuthenticationException(handlerErrors)); + } + + private void configureCasMockSTCreationToThrow(final Throwable e) throws Throwable { + when(this.casMock.grantServiceTicket(anyString(), any(Service.class))).thenThrow(e); + } + + private void configureCasMockToCreateValidST() throws Throwable { + final ServiceTicket st = mock(ServiceTicket.class); + when(st.getId()).thenReturn("ST-1"); + when(this.casMock.grantServiceTicket(anyString(), any(Service.class))).thenReturn(st); + } +} diff --git a/cas-server-support-rest/src/test/resources/log4j2.xml b/cas-server-support-rest/src/test/resources/log4j2.xml new file mode 100644 index 000000000000..8f14bd2a835d --- /dev/null +++ b/cas-server-support-rest/src/test/resources/log4j2.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-support-saml/NOTICE b/cas-server-support-saml/NOTICE new file mode 100644 index 000000000000..b3964843afb3 --- /dev/null +++ b/cas-server-support-saml/NOTICE @@ -0,0 +1,119 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apache Velocity under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS SAML Server and Validation Support under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + Bouncy Castle Provider under Bouncy Castle Licence + CDI APIs under Apache License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Lang under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + ESAPI under BSD or Creative Commons 3.0 BY-SA + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + HttpClient under Apache License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + JCL 1.1.1 implemented over SLF4J under MIT License + jdom under Apache style license + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR 105 - Java(TM) XML Digital Signature API under JDL license + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Not Yet Commons SSL under Apache License v2 + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + OpenSAML-J under The Apache Software License, Version 2.0 + OpenWS under The Apache Software License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + serializer under Apache License, Version 2.0 + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + Xalan Java under The Apache Software License, Version 2.0 + xercesImpl under Apache License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + XML Commons Resolver Component under The Apache Software License, Version 2.0 + XML Security under The Apache Software License, Version 2.0 + xml-apis under Apache License, Version 2.0 + XMLTooling-J under The Apache Software License, Version 2.0 + diff --git a/cas-server-support-saml/pom.xml b/cas-server-support-saml/pom.xml new file mode 100644 index 000000000000..e258bec915ba --- /dev/null +++ b/cas-server-support-saml/pom.xml @@ -0,0 +1,218 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-support-saml + jar + Apereo CAS SAML Server and Validation Support + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + org.opensaml + opensaml-core + ${opensaml.version} + compile + + + org.slf4j + log4j-over-slf4j + + + org.slf4j + slf4j-api + + + + + + net.shibboleth.utilities + java-support + ${shib.util.java.support.version} + compile + + + org.slf4j + slf4j-api + + + + + + org.opensaml + opensaml-saml-api + ${opensaml.version} + compile + + + org.slf4j + log4j-over-slf4j + + + org.slf4j + slf4j-api + + + org.bouncycastle + bcprov-jdk15on + + + + + + + org.opensaml + opensaml-saml-impl + ${opensaml.version} + compile + + + org.slf4j + log4j-over-slf4j + + + org.slf4j + slf4j-api + + + + + + org.opensaml + opensaml-soap-api + ${opensaml.version} + compile + + + org.slf4j + log4j-over-slf4j + + + org.slf4j + slf4j-api + + + + + + org.hibernate + hibernate-validator + runtime + + + + org.bouncycastle + bcprov-jdk15on + ${bouncycastle.version} + + + + net.shibboleth.idp + idp-profile-spring + ${opensaml.version} + + + commons-logging + commons-logging + + + org.slf4j + slf4j-api + + + org.bouncycastle + bcprov-jdk15on + + + ch.qos.logback + logback-classic + + + ch.qos.logback + logback-core + + + + + + org.quartz-scheduler + quartz + compile + + + + javax.xml + xmldsig + ${xmldsig.version} + compile + + + + jdom + jdom + ${jdom.version} + compile + + + + xml-apis + xml-apis + runtime + + + + org.jasig.cas + cas-server-core + ${project.version} + test-jar + test + + + + + ${project.parent.basedir} + 1.0 + 1.0 + 7.1.1 + + + + + shib-release + https://build.shibboleth.net/nexus/content/groups/public + + false + + + true + + + + diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/OpenSamlConfigBean.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/OpenSamlConfigBean.java new file mode 100644 index 000000000000..fd85db4b212a --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/OpenSamlConfigBean.java @@ -0,0 +1,88 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.saml; + + +import net.shibboleth.utilities.java.support.xml.ParserPool; +import org.opensaml.core.config.ConfigurationService; +import org.opensaml.core.config.InitializationException; +import org.opensaml.core.config.InitializationService; +import org.opensaml.core.xml.config.XMLObjectProviderRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.Assert; + +import javax.annotation.PostConstruct; +import javax.validation.constraints.NotNull; + +/** + * Load the opensaml config context. + * @author Misagh Moayyed + * @since 4.1 + */ +public final class OpenSamlConfigBean { + private static final Logger LOGGER = LoggerFactory.getLogger(OpenSamlConfigBean.class); + + @Autowired + @NotNull + private ParserPool parserPool; + + /** + * Instantiates the config bean. + */ + public OpenSamlConfigBean() {} + + /** + * Gets the configured parser pool. + * + * @return the parser pool + */ + public ParserPool getParserPool() { + return parserPool; + } + + /** + * Initialize opensaml. + */ + @PostConstruct + public void init() { + LOGGER.debug("Initializing OpenSaml configuration..."); + Assert.notNull(this.parserPool, "parserPool cannot be null"); + + try { + InitializationService.initialize(); + } catch (final InitializationException e) { + throw new RuntimeException("Exception initializing OpenSAML", e); + } + + XMLObjectProviderRegistry registry; + synchronized(ConfigurationService.class) { + registry = ConfigurationService.get(XMLObjectProviderRegistry.class); + if (registry == null) { + LOGGER.debug("XMLObjectProviderRegistry did not exist in ConfigurationService, will be created"); + registry = new XMLObjectProviderRegistry(); + ConfigurationService.register(XMLObjectProviderRegistry.class, registry); + } + } + + registry.setParserPool(parserPool); + } +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/SamlProtocolConstants.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/SamlProtocolConstants.java new file mode 100644 index 000000000000..4ba7b5524bb3 --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/SamlProtocolConstants.java @@ -0,0 +1,47 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.saml; + +/** + * Class that exposes relevant constants and parameters to + * the SAML protocol. These include attribute names, pre-defined + * values and expected request parameter names as is specified + * by the protocol. + * + * @author Misagh Moayyed + * @since 4.1 + */ +public interface SamlProtocolConstants { + /** Constant representing the saml request. */ + String PARAMETER_SAML_REQUEST = "SAMLRequest"; + + /** Constant representing the saml response. */ + String PARAMETER_SAML_RESPONSE = "SAMLResponse"; + + /** Constant representing the saml relay state. */ + String PARAMETER_SAML_RELAY_STATE = "RelayState"; + + /** Constant representing artifact. */ + String CONST_PARAM_ARTIFACT = "SAMLart"; + + /** Constant representing service. */ + String CONST_PARAM_TARGET = "TARGET"; + +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/authentication/SamlAuthenticationMetaDataPopulator.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/authentication/SamlAuthenticationMetaDataPopulator.java new file mode 100644 index 000000000000..62eaa729023a --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/authentication/SamlAuthenticationMetaDataPopulator.java @@ -0,0 +1,107 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.authentication; + +import org.jasig.cas.authentication.AuthenticationBuilder; +import org.jasig.cas.authentication.AuthenticationMetaDataPopulator; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.HttpBasedServiceCredential; +import org.jasig.cas.authentication.UsernamePasswordCredential; + +import java.util.HashMap; +import java.util.Map; + +/** + * AuthenticationMetaDataPopulator to retrieve the Authentication Type. + *

+ * Note: Authentication Methods are exposed under the key: + * samlAuthenticationStatement::authMethod in the Authentication + * attributes map. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.1 + */ +public class SamlAuthenticationMetaDataPopulator implements AuthenticationMetaDataPopulator { + + /** The Constant ATTRIBUTE_AUTHENTICATION_METHOD. */ + public static final String ATTRIBUTE_AUTHENTICATION_METHOD = "samlAuthenticationStatementAuthMethod"; + + /** The Constant AUTHN_METHOD_PASSWORD. */ + public static final String AUTHN_METHOD_PASSWORD = "urn:oasis:names:tc:SAML:1.0:am:password"; + + /** The Constant AUTHN_METHOD_SSL_TLS_CLIENT. */ + public static final String AUTHN_METHOD_SSL_TLS_CLIENT = "urn:ietf:rfc:2246"; + + /** The Constant AUTHN_METHOD_X509_PUBLICKEY. */ + public static final String AUTHN_METHOD_X509_PUBLICKEY = "urn:oasis:names:tc:SAML:1.0:am:X509-PKI"; + + /** The Constant AUTHN_METHOD_UNSPECIFIED. */ + public static final String AUTHN_METHOD_UNSPECIFIED = "urn:oasis:names:tc:SAML:1.0:am:unspecified"; + + private final Map authenticationMethods = new HashMap<>(); + + /** + * Instantiates a new SAML authentication meta data populator. + */ + public SamlAuthenticationMetaDataPopulator() { + this.authenticationMethods.put( + HttpBasedServiceCredential.class.getName(), + AUTHN_METHOD_SSL_TLS_CLIENT); + this.authenticationMethods.put( + UsernamePasswordCredential.class.getName(), + AUTHN_METHOD_PASSWORD); + + // Next two classes are in other modules, so avoid using Class#getName() to prevent circular dependency + this.authenticationMethods.put( + "org.jasig.cas.adaptors.trusted.authentication.principal.PrincipalBearingCredentials", + AUTHN_METHOD_UNSPECIFIED); + this.authenticationMethods.put( + "org.jasig.cas.adaptors.x509.authentication.principal.X509CertificateCredentials", + AUTHN_METHOD_X509_PUBLICKEY); + } + + @Override + public final void populateAttributes(final AuthenticationBuilder builder, final Credential credential) { + + final String credentialsClass = credential.getClass().getName(); + final String authenticationMethod = this.authenticationMethods.get(credentialsClass); + + builder.addAttribute(ATTRIBUTE_AUTHENTICATION_METHOD, authenticationMethod); + } + + @Override + public boolean supports(final Credential credential) { + return true; + } + + /** + * Map of user-defined mappings. Note it is possible to override the + * defaults. Mapping should be of the following type: + *

Package/Class Name as String -> Name SAML Type
+ *

+ * Example: ("org.jasig.cas.authentication.HttpBasedServiceCredential" + * -> SAMLAuthenticationStatement.AuthenticationMethod_SSL_TLS_Client) + * + * @param userDefinedMappings map of user defined authentication types. + */ + public void setUserDefinedMappings(final Map userDefinedMappings) { + this.authenticationMethods.putAll(userDefinedMappings); + } +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/authentication/principal/GoogleAccountsService.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/authentication/principal/GoogleAccountsService.java new file mode 100644 index 000000000000..45ae33e39874 --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/authentication/principal/GoogleAccountsService.java @@ -0,0 +1,217 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.authentication.principal; + + +import org.jasig.cas.authentication.principal.AbstractWebApplicationService; +import org.jasig.cas.authentication.principal.DefaultResponse; +import org.jasig.cas.authentication.principal.Response; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.support.saml.util.AbstractSaml20ObjectBuilder; +import org.jasig.cas.util.ISOStandardDateFormat; +import org.jdom.Document; +import org.springframework.util.StringUtils; +import org.jasig.cas.support.saml.SamlProtocolConstants; +import org.jasig.cas.support.saml.util.GoogleSaml20ObjectBuilder; +import org.joda.time.DateTime; +import org.opensaml.saml.saml2.core.Assertion; +import org.opensaml.saml.saml2.core.AuthnContext; +import org.opensaml.saml.saml2.core.AuthnStatement; +import org.opensaml.saml.saml2.core.Conditions; +import org.opensaml.saml.saml2.core.NameID; +import org.opensaml.saml.saml2.core.StatusCode; +import org.opensaml.saml.saml2.core.Subject; +import javax.servlet.http.HttpServletRequest; +import java.io.StringWriter; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.HashMap; +import java.util.Map; + +import org.jdom.Element; +/** + * Implementation of a Service that supports Google Accounts (eventually a more + * generic SAML2 support will come). + * + * @author Scott Battaglia + * @since 3.1 + */ +public class GoogleAccountsService extends AbstractWebApplicationService { + + private static final long serialVersionUID = 6678711809842282833L; + private static final int INFLATED_BYTE_ARRAY_LENGTH = 10000; + private static final int DEFLATED_BYTE_ARRAY_BUFFER_LENGTH = 1024; + private static final int SAML_RESPONSE_RANDOM_ID_LENGTH = 20; + private static final int SAML_RESPONSE_ID_LENGTH = 40; + private static final int HEX_HIGH_BITS_BITWISE_FLAG = 0x0f; + private static final int HEX_RIGHT_SHIFT_COEFFICIENT = 4; + + private static final GoogleSaml20ObjectBuilder BUILDER = new GoogleSaml20ObjectBuilder(); + + private final String relayState; + + private final PublicKey publicKey; + + private final PrivateKey privateKey; + + private final String requestId; + + private final ServicesManager servicesManager; + + /** + * Instantiates a new google accounts service. + * + * @param id the id + * @param relayState the relay state + * @param requestId the request id + * @param privateKey the private key + * @param publicKey the public key + * @param servicesManager the services manager + */ + protected GoogleAccountsService(final String id, final String relayState, final String requestId, + final PrivateKey privateKey, final PublicKey publicKey, final ServicesManager servicesManager) { + this(id, id, null, relayState, requestId, privateKey, publicKey, servicesManager); + } + + /** + * Instantiates a new google accounts service. + * + * @param id the id + * @param originalUrl the original url + * @param artifactId the artifact id + * @param relayState the relay state + * @param requestId the request id + * @param privateKey the private key + * @param publicKey the public key + * @param servicesManager the services manager + */ + protected GoogleAccountsService(final String id, final String originalUrl, + final String artifactId, final String relayState, final String requestId, + final PrivateKey privateKey, final PublicKey publicKey, + final ServicesManager servicesManager) { + super(id, originalUrl, artifactId); + this.relayState = relayState; + this.privateKey = privateKey; + this.publicKey = publicKey; + this.requestId = requestId; + this.servicesManager = servicesManager; + + + } + + /** + * Creates the service from request. + * + * @param request the request + * @param privateKey the private key + * @param publicKey the public key + * @param servicesManager the services manager + * @return the google accounts service + */ + public static GoogleAccountsService createServiceFrom( + final HttpServletRequest request, final PrivateKey privateKey, + final PublicKey publicKey, final ServicesManager servicesManager) { + final String relayState = request.getParameter(SamlProtocolConstants.PARAMETER_SAML_RELAY_STATE); + + final String xmlRequest = BUILDER.decodeSamlAuthnRequest( + request.getParameter(SamlProtocolConstants.PARAMETER_SAML_REQUEST)); + + if (!StringUtils.hasText(xmlRequest)) { + return null; + } + + final Document document = AbstractSaml20ObjectBuilder.constructDocumentFromXml(xmlRequest); + + if (document == null) { + return null; + } + + final Element root = document.getRootElement(); + final String assertionConsumerServiceUrl = root.getAttributeValue("AssertionConsumerServiceURL"); + final String requestId = root.getAttributeValue("ID"); + + return new GoogleAccountsService(assertionConsumerServiceUrl, + relayState, requestId, privateKey, publicKey, servicesManager); + } + + @Override + public Response getResponse(final String ticketId) { + final Map parameters = new HashMap<>(); + final String samlResponse = constructSamlResponse(); + final String signedResponse = BUILDER.signSamlResponse(samlResponse, + this.privateKey, this.publicKey); + parameters.put(SamlProtocolConstants.PARAMETER_SAML_RESPONSE, signedResponse); + parameters.put(SamlProtocolConstants.PARAMETER_SAML_RELAY_STATE, this.relayState); + + return DefaultResponse.getPostResponse(getOriginalUrl(), parameters); + } + + /** + * Return true if the service is already logged out. + * + * @return true if the service is already logged out. + */ + @Override + public boolean isLoggedOutAlready() { + return true; + } + + /** + * Construct SAML response. + * See this reference for more info. + * @return the SAML response + */ + private String constructSamlResponse() { + final DateTime currentDateTime = DateTime.parse(new ISOStandardDateFormat().getCurrentDateAndTime()); + final DateTime notBeforeIssueInstant = DateTime.parse("2003-04-17T00:46:02Z"); + + final RegisteredService svc = this.servicesManager.findServiceBy(this); + final String userId = svc.getUsernameAttributeProvider().resolveUsername(getPrincipal(), this); + + final org.opensaml.saml.saml2.core.Response response = BUILDER.newResponse( + BUILDER.generateSecureRandomId(), + currentDateTime, + getId(), this); + response.setStatus(BUILDER.newStatus(StatusCode.SUCCESS, null)); + + final AuthnStatement authnStatement = BUILDER.newAuthnStatement( + AuthnContext.PASSWORD_AUTHN_CTX, currentDateTime); + final Assertion assertion = BUILDER.newAssertion(authnStatement, + "https://www.opensaml.org/IDP", + notBeforeIssueInstant, BUILDER.generateSecureRandomId()); + + final Conditions conditions = BUILDER.newConditions(notBeforeIssueInstant, + currentDateTime, getId()); + assertion.setConditions(conditions); + + final Subject subject = BUILDER.newSubject(NameID.EMAIL, userId, + getId(), currentDateTime, this.requestId); + assertion.setSubject(subject); + + response.getAssertions().add(assertion); + + final StringWriter writer = new StringWriter(); + BUILDER.marshalSamlXmlObject(response, writer); + + final String result = writer.toString(); + logger.debug("Generated Google SAML response: {}", result); + return result; + } +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/authentication/principal/SamlService.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/authentication/principal/SamlService.java new file mode 100644 index 000000000000..ff4a93c9eeb9 --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/authentication/principal/SamlService.java @@ -0,0 +1,197 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.authentication.principal; + +import org.jasig.cas.authentication.principal.AbstractWebApplicationService; +import org.jasig.cas.authentication.principal.DefaultResponse; +import org.jasig.cas.authentication.principal.Response; +import org.jasig.cas.support.saml.SamlProtocolConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.io.BufferedReader; +import java.util.HashMap; +import java.util.Map; + +/** + * Class to represent that this service wants to use SAML. We use this in + * combination with the CentralAuthenticationServiceImpl to choose the right + * UniqueTicketIdGenerator. + * + * @author Scott Battaglia + * @since 3.1 + */ +public final class SamlService extends AbstractWebApplicationService { + + private static final Logger LOGGER = LoggerFactory.getLogger(SamlService.class); + + /** + * Unique Id for serialization. + */ + private static final long serialVersionUID = -6867572626767140223L; + + private static final String CONST_START_ARTIFACT_XML_TAG_NO_NAMESPACE = ""; + + private static final String CONST_END_ARTIFACT_XML_TAG_NO_NAMESPACE = ""; + + private static final String CONST_START_ARTIFACT_XML_TAG = ""; + + private static final String CONST_END_ARTIFACT_XML_TAG = ""; + + private static final int CONST_REQUEST_ID_LENGTH = 11; + + private String requestId; + + /** + * Instantiates a new SAML service. + * + * @param id the service id + */ + protected SamlService(final String id) { + super(id, id, null); + } + + /** + * Instantiates a new SAML service. + * + * @param id the service id + * @param originalUrl the original url + * @param artifactId the artifact id + * @param requestId the request id + */ + protected SamlService(final String id, final String originalUrl, + final String artifactId, final String requestId) { + super(id, originalUrl, artifactId); + this.requestId = requestId; + } + + public String getRequestID() { + return this.requestId; + } + + /** + * Creates the SAML service from the request. + * + * @param request the request + * @return the SAML service + */ + public static SamlService createServiceFrom( + final HttpServletRequest request) { + final String service = request.getParameter(SamlProtocolConstants.CONST_PARAM_TARGET); + final String artifactId; + final String requestBody = getRequestBody(request); + final String requestId; + + if (!StringUtils.hasText(service) && !StringUtils.hasText(requestBody)) { + LOGGER.debug("Request does not specify a {} or request body is empty", + SamlProtocolConstants.CONST_PARAM_TARGET); + return null; + } + + final String id = cleanupUrl(service); + + if (StringUtils.hasText(requestBody)) { + + final String tagStart; + final String tagEnd; + if (requestBody.contains(CONST_START_ARTIFACT_XML_TAG)) { + tagStart = CONST_START_ARTIFACT_XML_TAG; + tagEnd = CONST_END_ARTIFACT_XML_TAG; + } else { + tagStart = CONST_START_ARTIFACT_XML_TAG_NO_NAMESPACE; + tagEnd = CONST_END_ARTIFACT_XML_TAG_NO_NAMESPACE; + } + final int startTagLocation = requestBody.indexOf(tagStart); + final int artifactStartLocation = startTagLocation + tagStart.length(); + final int endTagLocation = requestBody.indexOf(tagEnd); + + artifactId = requestBody.substring(artifactStartLocation, endTagLocation).trim(); + + // is there a request id? + requestId = extractRequestId(requestBody); + } else { + artifactId = null; + requestId = null; + } + + LOGGER.debug("Attempted to extract Request from HttpServletRequest. Results:"); + LOGGER.debug("Request Body: {}", requestBody); + LOGGER.debug("Extracted ArtifactId: {}", artifactId); + LOGGER.debug("Extracted Request Id: {}", requestId); + + return new SamlService(id, service, artifactId, requestId); + } + + @Override + public Response getResponse(final String ticketId) { + final Map parameters = new HashMap<>(); + parameters.put(SamlProtocolConstants.CONST_PARAM_ARTIFACT, ticketId); + return DefaultResponse.getRedirectResponse(getOriginalUrl(), parameters); + } + + /** + * Extract request id from the body. + * + * @param requestBody the request body + * @return the string + */ + protected static String extractRequestId(final String requestBody) { + if (!requestBody.contains("RequestID")) { + LOGGER.debug("Request body does not contain a request id"); + return null; + } + + try { + final int position = requestBody.indexOf("RequestID=\"") + CONST_REQUEST_ID_LENGTH; + final int nextPosition = requestBody.indexOf('"', position); + + return requestBody.substring(position, nextPosition); + } catch (final Exception e) { + LOGGER.debug("Exception parsing RequestID from request.", e); + return null; + } + } + + /** + * Gets the request body from the request. + * + * @param request the request + * @return the request body + */ + protected static String getRequestBody(final HttpServletRequest request) { + final StringBuilder builder = new StringBuilder(); + try (final BufferedReader reader = request.getReader()) { + + if (reader == null) { + LOGGER.debug("Request body could not be read because it's empty."); + return null; + } + String line; + while ((line = reader.readLine()) != null) { + builder.append(line); + } + return builder.toString(); + } catch (final Exception e) { + LOGGER.trace("Could not obtain the saml request body from the http request", e); + return null; + } + } +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/AbstractSaml20ObjectBuilder.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/AbstractSaml20ObjectBuilder.java new file mode 100644 index 000000000000..5323e8650e21 --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/AbstractSaml20ObjectBuilder.java @@ -0,0 +1,268 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.saml.util; + + +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.support.saml.authentication.principal.SamlService; +import org.jasig.cas.util.CompressionUtils; +import org.joda.time.DateTime; +import org.opensaml.saml.common.SAMLVersion; +import org.opensaml.saml.saml2.core.Assertion; +import org.opensaml.saml.saml2.core.Audience; +import org.opensaml.saml.saml2.core.AudienceRestriction; +import org.opensaml.saml.saml2.core.AuthnContext; +import org.opensaml.saml.saml2.core.AuthnContextClassRef; +import org.opensaml.saml.saml2.core.AuthnStatement; +import org.opensaml.saml.saml2.core.Conditions; +import org.opensaml.saml.saml2.core.Issuer; +import org.opensaml.saml.saml2.core.NameID; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.core.Status; +import org.opensaml.saml.saml2.core.StatusCode; +import org.opensaml.saml.saml2.core.StatusMessage; +import org.opensaml.saml.saml2.core.Subject; +import org.opensaml.saml.saml2.core.SubjectConfirmation; +import org.opensaml.saml.saml2.core.SubjectConfirmationData; +import org.springframework.util.StringUtils; + +import java.security.SecureRandom; + +/** + * This is {@link AbstractSaml20ObjectBuilder}. + * to build saml2 objects. + * @author Misagh Moayyed mmoayyed@unicon.net + * @since 4.1 + */ +public abstract class AbstractSaml20ObjectBuilder extends AbstractSamlObjectBuilder { + private static final int HEX_HIGH_BITS_BITWISE_FLAG = 0x0f; + + /** + * Gets name id. + * + * @param nameIdFormat the name id format + * @param nameIdValue the name id value + * @return the name iD + */ + protected NameID getNameID(final String nameIdFormat, final String nameIdValue) { + final NameID nameId = newSamlObject(NameID.class); + nameId.setFormat(nameIdFormat); + nameId.setValue(nameIdValue); + return nameId; + } + + /** + * Create a new SAML response object. + * @param id the id + * @param issueInstant the issue instant + * @param recipient the recipient + * @param service the service + * @return the response + */ + public Response newResponse(final String id, final DateTime issueInstant, + final String recipient, final WebApplicationService service) { + + final Response samlResponse = newSamlObject(Response.class); + samlResponse.setID(id); + samlResponse.setIssueInstant(issueInstant); + samlResponse.setVersion(SAMLVersion.VERSION_20); + if (service instanceof SamlService) { + final SamlService samlService = (SamlService) service; + + if (samlService.getRequestID() != null) { + samlResponse.setInResponseTo(samlService.getRequestID()); + } + } + return samlResponse; + } + + /** + * Create a new SAML status object. + * + * @param codeValue the code value + * @param statusMessage the status message + * @return the status + */ + public Status newStatus(final String codeValue, final String statusMessage) { + final Status status = newSamlObject(Status.class); + final StatusCode code = newSamlObject(StatusCode.class); + code.setValue(codeValue); + status.setStatusCode(code); + if (StringUtils.hasText(statusMessage)) { + final StatusMessage message = newSamlObject(StatusMessage.class); + message.setMessage(statusMessage); + status.setStatusMessage(message); + } + return status; + } + + /** + * Create a new SAML1 response object. + * + * @param authnStatement the authn statement + * @param issuer the issuer + * @param issuedAt the issued at + * @param id the id + * @return the assertion + */ + public Assertion newAssertion(final AuthnStatement authnStatement, final String issuer, + final DateTime issuedAt, final String id) { + final Assertion assertion = newSamlObject(Assertion.class); + assertion.setID(id); + assertion.setIssueInstant(issuedAt); + assertion.setIssuer(newIssuer(issuer)); + assertion.getAuthnStatements().add(authnStatement); + return assertion; + } + + /** + * New issuer. + * + * @param issuerValue the issuer + * @return the issuer + */ + public Issuer newIssuer(final String issuerValue) { + final Issuer issuer = newSamlObject(Issuer.class); + issuer.setValue(issuerValue); + return issuer; + } + + /** + * New authn statement. + * + * @param contextClassRef the context class ref such as {@link AuthnContext#PASSWORD_AUTHN_CTX} + * @param authnInstant the authn instant + * @return the authn statement + */ + public AuthnStatement newAuthnStatement(final String contextClassRef, final DateTime authnInstant) { + final AuthnStatement stmt = newSamlObject(AuthnStatement.class); + final AuthnContext ctx = newSamlObject(AuthnContext.class); + + final AuthnContextClassRef classRef = newSamlObject(AuthnContextClassRef.class); + classRef.setAuthnContextClassRef(contextClassRef); + + ctx.setAuthnContextClassRef(classRef); + stmt.setAuthnContext(ctx); + stmt.setAuthnInstant(authnInstant); + + return stmt; + } + + /** + * New conditions element. + * + * @param notBefore the not before + * @param notOnOrAfter the not on or after + * @param audienceUri the service id + * @return the conditions + */ + public Conditions newConditions(final DateTime notBefore, final DateTime notOnOrAfter, final String audienceUri) { + final Conditions conditions = newSamlObject(Conditions.class); + conditions.setNotBefore(notBefore); + conditions.setNotOnOrAfter(notOnOrAfter); + + final AudienceRestriction audienceRestriction = newSamlObject(AudienceRestriction.class); + final Audience audience = newSamlObject(Audience.class); + audience.setAudienceURI(audienceUri); + audienceRestriction.getAudiences().add(audience); + conditions.getAudienceRestrictions().add(audienceRestriction); + return conditions; + } + + /** + * New subject element. + * + * @param nameIdFormat the name id format + * @param nameIdValue the name id value + * @param recipient the recipient + * @param notOnOrAfter the not on or after + * @param inResponseTo the in response to + * @return the subject + */ + public Subject newSubject(final String nameIdFormat, final String nameIdValue, + final String recipient, final DateTime notOnOrAfter, + final String inResponseTo) { + + final SubjectConfirmation confirmation = newSamlObject(SubjectConfirmation.class); + confirmation.setMethod(SubjectConfirmation.METHOD_BEARER); + + final SubjectConfirmationData data = newSamlObject(SubjectConfirmationData.class); + data.setRecipient(recipient); + data.setNotOnOrAfter(notOnOrAfter); + data.setInResponseTo(inResponseTo); + + confirmation.setSubjectConfirmationData(data); + + final Subject subject = newSamlObject(Subject.class); + subject.setNameID(getNameID(nameIdFormat, nameIdValue)); + subject.getSubjectConfirmations().add(confirmation); + return subject; + } + + @Override + public String generateSecureRandomId() { + final SecureRandom generator = new SecureRandom(); + final char[] charMappings = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p'}; + + final int charsLength = 40; + final int generatorBytesLength = 20; + final int shiftLength = 4; + + // 160 bits + final byte[] bytes = new byte[generatorBytesLength]; + generator.nextBytes(bytes); + + final char[] chars = new char[charsLength]; + for (int i = 0; i < bytes.length; i++) { + final int left = bytes[i] >> shiftLength & HEX_HIGH_BITS_BITWISE_FLAG; + final int right = bytes[i] & HEX_HIGH_BITS_BITWISE_FLAG; + chars[i * 2] = charMappings[left]; + chars[i * 2 + 1] = charMappings[right]; + } + return String.valueOf(chars); + } + + /** + * Decode authn request xml. + * + * @param encodedRequestXmlString the encoded request xml string + * @return the request + */ + public String decodeSamlAuthnRequest(final String encodedRequestXmlString) { + if (StringUtils.isEmpty(encodedRequestXmlString)) { + return null; + } + + final byte[] decodedBytes = CompressionUtils.decodeBase64ToByteArray(encodedRequestXmlString); + if (decodedBytes == null) { + return null; + } + + final String inflated = CompressionUtils.inflate(decodedBytes); + if (!StringUtils.isEmpty(inflated)) { + return inflated; + } + + return CompressionUtils.decodeByteArrayToString(decodedBytes); + } +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/AbstractSamlObjectBuilder.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/AbstractSamlObjectBuilder.java new file mode 100644 index 000000000000..a67df421ccd5 --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/AbstractSamlObjectBuilder.java @@ -0,0 +1,395 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.saml.util; + +import org.bouncycastle.util.encoders.Hex; +import org.jdom.Document; +import org.jdom.input.DOMBuilder; +import org.jdom.input.SAXBuilder; +import org.jdom.output.XMLOutputter; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.saml.common.SAMLObject; +import org.opensaml.saml.common.SAMLObjectBuilder; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.io.Marshaller; +import org.opensaml.core.xml.io.MarshallerFactory; +import org.opensaml.core.xml.schema.XSString; +import org.opensaml.core.xml.schema.impl.XSStringBuilder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import javax.xml.crypto.dsig.CanonicalizationMethod; +import javax.xml.crypto.dsig.DigestMethod; +import javax.xml.crypto.dsig.Reference; +import javax.xml.crypto.dsig.SignatureMethod; +import javax.xml.crypto.dsig.SignedInfo; +import javax.xml.crypto.dsig.Transform; +import javax.xml.crypto.dsig.XMLSignature; +import javax.xml.crypto.dsig.XMLSignatureFactory; +import javax.xml.crypto.dsig.dom.DOMSignContext; +import javax.xml.crypto.dsig.keyinfo.KeyInfo; +import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory; +import javax.xml.crypto.dsig.keyinfo.KeyValue; +import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; +import javax.xml.crypto.dsig.spec.TransformParameterSpec; +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.ByteArrayInputStream; +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.nio.charset.Charset; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.util.Collections; +import java.util.List; +/** + * An abstract builder to serve as the template handler + * for SAML1 and SAML2 responses. + * + * @author Misagh Moayyed mmoayyed@unicon.net + * @since 4.1 + */ +public abstract class AbstractSamlObjectBuilder { + /** + * The constant DEFAULT_ELEMENT_NAME_FIELD. + */ + protected static final String DEFAULT_ELEMENT_NAME_FIELD = "DEFAULT_ELEMENT_NAME"; + + /** + * The constant DEFAULT_ELEMENT_LOCAL_NAME_FIELD. + */ + protected static final String DEFAULT_ELEMENT_LOCAL_NAME_FIELD = "DEFAULT_ELEMENT_LOCAL_NAME"; + + private static final int RANDOM_ID_SIZE = 16; + + private static final String SIGNATURE_FACTORY_PROVIDER_CLASS = "org.jcp.xml.dsig.internal.dom.XMLDSigRI"; + + /** Logger instance. **/ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * Create a new SAML object. + * + * @param the generic type + * @param objectType the object type + * @return the t + */ + public final T newSamlObject(final Class objectType) { + final QName qName = getSamlObjectQName(objectType); + final SAMLObjectBuilder builder = (SAMLObjectBuilder) + XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName); + if (builder == null) { + throw new IllegalStateException("No SAMLObjectBuilder registered for class " + objectType.getName()); + } + return objectType.cast(builder.buildObject(qName)); + } + + /** + * Gets saml object QName. + * + * @param objectType the object type + * @return the saml object QName + * @throws RuntimeException the exception + */ + public QName getSamlObjectQName(final Class objectType) throws RuntimeException { + try { + final Field f = objectType.getField(DEFAULT_ELEMENT_NAME_FIELD); + return (QName) f.get(null); + } catch (final NoSuchFieldException e) { + throw new IllegalStateException("Cannot find field " + objectType.getName() + '.' + DEFAULT_ELEMENT_NAME_FIELD); + } catch (final IllegalAccessException e) { + throw new IllegalStateException("Cannot access field " + objectType.getName() + '.' + DEFAULT_ELEMENT_NAME_FIELD); + } + } + + /** + * Build the saml object based on its QName. + * + * @param objectType the object + * @param qName the QName + * @param the object type + * @return the saml object + */ + private T newSamlObject(final Class objectType, final QName qName) { + final SAMLObjectBuilder builder = (SAMLObjectBuilder) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName); + if (builder == null) { + throw new IllegalStateException("No SAMLObjectBuilder registered for class " + objectType.getName()); + } + return objectType.cast(builder.buildObject()); + } + + /** + * New attribute value. + * + * @param value the value + * @param elementName the element name + * @return the xS string + */ + protected final XSString newAttributeValue(final Object value, final QName elementName) { + final XSStringBuilder attrValueBuilder = new XSStringBuilder(); + final XSString stringValue = attrValueBuilder.buildObject(elementName, XSString.TYPE_NAME); + if (value instanceof String) { + stringValue.setValue((String) value); + } else { + stringValue.setValue(value.toString()); + } + return stringValue; + } + + /** + * Generate a secure random id. + * + * @return the secure id string + */ + public String generateSecureRandomId() { + try { + final SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); + final byte[] buf = new byte[RANDOM_ID_SIZE]; + random.nextBytes(buf); + return "_".concat(new String(Hex.encode(buf))); + } catch (final Exception e) { + throw new IllegalStateException("Cannot create secure random ID generator for SAML message IDs.", e); + } + } + + /** + * Marshal the saml xml object to raw xml. + * + * @param object the object + * @param writer the writer + * @return the xml string + */ + public String marshalSamlXmlObject(final XMLObject object, final StringWriter writer) { + try { + final MarshallerFactory marshallerFactory = XMLObjectProviderRegistrySupport.getMarshallerFactory(); + final Marshaller marshaller = marshallerFactory.getMarshaller(object); + if (marshaller == null) { + throw new IllegalArgumentException("Cannot obtain marshaller for object " + object.getElementQName()); + } + final Element element = marshaller.marshall(object); + element.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", SAMLConstants.SAML20_NS); + element.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#"); + + final TransformerFactory transFactory = TransformerFactory.newInstance(); + final Transformer transformer = transFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.transform(new DOMSource(element), new StreamResult(writer)); + return writer.toString(); + } catch (final Exception e) { + throw new IllegalStateException("An error has occurred while marshalling SAML object to xml", e); + } + } + + /** + * Sign SAML response. + * + * @param samlResponse the SAML response + * @param privateKey the private key + * @param publicKey the public key + * @return the response + */ + public final String signSamlResponse(final String samlResponse, + final PrivateKey privateKey, final PublicKey publicKey) { + final Document doc = constructDocumentFromXml(samlResponse); + + if (doc != null) { + final org.jdom.Element signedElement = signSamlElement(doc.getRootElement(), + privateKey, publicKey); + doc.setRootElement((org.jdom.Element) signedElement.detach()); + return new XMLOutputter().outputString(doc); + } + throw new RuntimeException("Error signing SAML Response: Null document"); + } + + /** + * Construct document from xml string. + * + * @param xmlString the xml string + * @return the document + */ + public static Document constructDocumentFromXml(final String xmlString) { + try { + final SAXBuilder builder = new SAXBuilder(); + builder.setFeature("http://xml.org/sax/features/external-general-entities", false); + builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + return builder + .build(new ByteArrayInputStream(xmlString.getBytes(Charset.defaultCharset()))); + } catch (final Exception e) { + return null; + } + } + + /** + * Sign SAML element. + * + * @param element the element + * @param privKey the priv key + * @param pubKey the pub key + * @return the element + */ + private org.jdom.Element signSamlElement(final org.jdom.Element element, final PrivateKey privKey, + final PublicKey pubKey) { + try { + final String providerName = System.getProperty("jsr105Provider", + SIGNATURE_FACTORY_PROVIDER_CLASS); + + final XMLSignatureFactory sigFactory = XMLSignatureFactory + .getInstance("DOM", (Provider) Class.forName(providerName) + .newInstance()); + + final List envelopedTransform = Collections + .singletonList(sigFactory.newTransform(Transform.ENVELOPED, + (TransformParameterSpec) null)); + + final Reference ref = sigFactory.newReference("", sigFactory + .newDigestMethod(DigestMethod.SHA1, null), envelopedTransform, + null, null); + + // Create the SignatureMethod based on the type of key + final SignatureMethod signatureMethod; + if (pubKey instanceof DSAPublicKey) { + signatureMethod = sigFactory.newSignatureMethod( + SignatureMethod.DSA_SHA1, null); + } else if (pubKey instanceof RSAPublicKey) { + signatureMethod = sigFactory.newSignatureMethod( + SignatureMethod.RSA_SHA1, null); + } else { + throw new RuntimeException("Error signing SAML element: Unsupported type of key"); + } + + final CanonicalizationMethod canonicalizationMethod = sigFactory + .newCanonicalizationMethod( + CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS, + (C14NMethodParameterSpec) null); + + // Create the SignedInfo + final SignedInfo signedInfo = sigFactory.newSignedInfo( + canonicalizationMethod, signatureMethod, Collections + .singletonList(ref)); + + // Create a KeyValue containing the DSA or RSA PublicKey + final KeyInfoFactory keyInfoFactory = sigFactory + .getKeyInfoFactory(); + final KeyValue keyValuePair = keyInfoFactory.newKeyValue(pubKey); + + // Create a KeyInfo and add the KeyValue to it + final KeyInfo keyInfo = keyInfoFactory.newKeyInfo(Collections + .singletonList(keyValuePair)); + // Convert the JDOM document to w3c (Java XML signature API requires + // w3c representation) + final org.w3c.dom.Element w3cElement = toDom(element); + + // Create a DOMSignContext and specify the DSA/RSA PrivateKey and + // location of the resulting XMLSignature's parent element + final DOMSignContext dsc = new DOMSignContext(privKey, w3cElement); + + final org.w3c.dom.Node xmlSigInsertionPoint = getXmlSignatureInsertLocation(w3cElement); + dsc.setNextSibling(xmlSigInsertionPoint); + + // Marshal, generate (and sign) the enveloped signature + final XMLSignature signature = sigFactory.newXMLSignature(signedInfo, + keyInfo); + signature.sign(dsc); + + return toJdom(w3cElement); + + } catch (final Exception e) { + throw new RuntimeException("Error signing SAML element: " + + e.getMessage(), e); + } + } + + /** + * Gets the xml signature insert location. + * + * @param elem the elem + * @return the xml signature insert location + */ + private static Node getXmlSignatureInsertLocation(final org.w3c.dom.Element elem) { + org.w3c.dom.Node insertLocation = null; + org.w3c.dom.NodeList nodeList = elem.getElementsByTagNameNS( + SAMLConstants.SAML20P_NS, "Extensions"); + if (nodeList.getLength() != 0) { + insertLocation = nodeList.item(nodeList.getLength() - 1); + } else { + nodeList = elem.getElementsByTagNameNS(SAMLConstants.SAML20P_NS, "Status"); + insertLocation = nodeList.item(nodeList.getLength() - 1); + } + return insertLocation; + } + + /** + * Convert the received jdom element to an Element. + * + * @param element the element + * @return the org.w3c.dom. element + */ + private org.w3c.dom.Element toDom(final org.jdom.Element element) { + return toDom(element.getDocument()).getDocumentElement(); + } + + /** + * Convert the received jdom doc to a Document element. + * + * @param doc the doc + * @return the org.w3c.dom. document + */ + private org.w3c.dom.Document toDom(final Document doc) { + try { + final XMLOutputter xmlOutputter = new XMLOutputter(); + final StringWriter elemStrWriter = new StringWriter(); + xmlOutputter.output(doc, elemStrWriter); + final byte[] xmlBytes = elemStrWriter.toString().getBytes(Charset.defaultCharset()); + final DocumentBuilderFactory dbf = DocumentBuilderFactory + .newInstance(); + dbf.setNamespaceAware(true); + return dbf.newDocumentBuilder().parse( + new ByteArrayInputStream(xmlBytes)); + } catch (final Exception e) { + logger.trace(e.getMessage(), e); + return null; + } + } + + /** + * Convert to a jdom element. + * + * @param e the e + * @return the element + */ + private static org.jdom.Element toJdom(final org.w3c.dom.Element e) { + return new DOMBuilder().build(e); + } +} + diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/CasHTTPSOAP11Encoder.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/CasHTTPSOAP11Encoder.java new file mode 100644 index 000000000000..e655d969352d --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/CasHTTPSOAP11Encoder.java @@ -0,0 +1,77 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.util; + +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.saml.saml1.binding.encoding.impl.HTTPSOAP11Encoder; +import org.opensaml.soap.common.SOAPObjectBuilder; +import org.opensaml.soap.soap11.Body; +import org.opensaml.soap.soap11.Envelope; +import org.opensaml.soap.util.SOAPConstants; +import org.opensaml.core.xml.XMLObjectBuilderFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Override OpenSAML {@link HTTPSOAP11Encoder} such that SOAP-ENV XML namespace prefix is used for SOAP envelope + * elements. This is needed for backward compatibility with certain CAS clients (e.g. Java CAS client). + * + * @author Marvin S. Addison + * @since 3.5.1 + * @deprecated As of 4.1, the class name is abbreviated in a way that is not per camel-casing + * standards and will be renamed in the future. + */ +@Deprecated +public final class CasHTTPSOAP11Encoder extends HTTPSOAP11Encoder { + private static final String OPENSAML_11_SOAP_NS_PREFIX = "SOAP-ENV"; + + private static final Logger LOGGER = LoggerFactory.getLogger(CasHTTPSOAP11Encoder.class); + + /** + * Instantiates a new encoder. + */ + public CasHTTPSOAP11Encoder() { + super(); + } + + @Override + protected void buildAndStoreSOAPMessage(final XMLObject payload) { + final XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory(); + + final SOAPObjectBuilder envBuilder = + (SOAPObjectBuilder) builderFactory.getBuilder(Envelope.DEFAULT_ELEMENT_NAME); + final Envelope envelope = envBuilder.buildObject( + SOAPConstants.SOAP11_NS, Envelope.DEFAULT_ELEMENT_LOCAL_NAME, OPENSAML_11_SOAP_NS_PREFIX); + + final SOAPObjectBuilder bodyBuilder = + (SOAPObjectBuilder) builderFactory.getBuilder(Body.DEFAULT_ELEMENT_NAME); + final Body body = bodyBuilder.buildObject( + SOAPConstants.SOAP11_NS, Body.DEFAULT_ELEMENT_LOCAL_NAME, OPENSAML_11_SOAP_NS_PREFIX); + + if(!body.getUnknownXMLObjects().isEmpty()) { + LOGGER.warn("Existing SOAP Envelope Body already contained children"); + } + + body.getUnknownXMLObjects().add(payload); + envelope.setBody(body); + this.storeSOAPEnvelope(envelope); + } + +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/GoogleSaml20ObjectBuilder.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/GoogleSaml20ObjectBuilder.java new file mode 100644 index 000000000000..f0f55b86add0 --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/GoogleSaml20ObjectBuilder.java @@ -0,0 +1,54 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.saml.util; + +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.core.Status; +import org.opensaml.saml.saml2.core.StatusCode; + +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import java.lang.reflect.Field; + +/** + * This is {@link org.jasig.cas.support.saml.util.GoogleSaml20ObjectBuilder} that + * attempts to build the saml response. QName based on the spec described here: + * https://developers.google.com/google-apps/sso/saml_reference_implementation_web#samlReferenceImplementationWebSetupChangeDomain + * @author Misagh Moayyed mmoayyed@unicon.net + * @since 4.1.0 + */ +public class GoogleSaml20ObjectBuilder extends AbstractSaml20ObjectBuilder { + @Override + public final QName getSamlObjectQName(final Class objectType) throws RuntimeException { + try { + final Field f = objectType.getField(DEFAULT_ELEMENT_LOCAL_NAME_FIELD); + final String name = f.get(null).toString(); + + if (objectType.equals(Response.class) || objectType.equals(Status.class) + || objectType.equals(StatusCode.class)) { + return new QName(SAMLConstants.SAML20P_NS, name, "samlp"); + } + return new QName(SAMLConstants.SAML20_NS, name, XMLConstants.DEFAULT_NS_PREFIX); + } catch (final Exception e){ + throw new IllegalStateException("Cannot access field " + objectType.getName() + '.' + DEFAULT_ELEMENT_LOCAL_NAME_FIELD); + } + } +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/Saml10ObjectBuilder.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/Saml10ObjectBuilder.java new file mode 100644 index 000000000000..b18675409755 --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/Saml10ObjectBuilder.java @@ -0,0 +1,263 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.saml.util; + +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.support.saml.authentication.SamlAuthenticationMetaDataPopulator; +import org.jasig.cas.support.saml.authentication.principal.SamlService; +import org.joda.time.DateTime; +import org.opensaml.messaging.context.MessageContext; +import org.opensaml.saml.common.SAMLObject; +import org.opensaml.saml.common.SAMLVersion; +import org.opensaml.saml.saml1.binding.encoding.impl.HTTPSOAP11Encoder; +import org.opensaml.saml.saml1.core.Assertion; +import org.opensaml.saml.saml1.core.Attribute; +import org.opensaml.saml.saml1.core.AttributeStatement; +import org.opensaml.saml.saml1.core.AttributeValue; +import org.opensaml.saml.saml1.core.Audience; +import org.opensaml.saml.saml1.core.AudienceRestrictionCondition; +import org.opensaml.saml.saml1.core.AuthenticationStatement; +import org.opensaml.saml.saml1.core.Conditions; +import org.opensaml.saml.saml1.core.ConfirmationMethod; +import org.opensaml.saml.saml1.core.NameIdentifier; +import org.opensaml.saml.saml1.core.Response; +import org.opensaml.saml.saml1.core.Status; +import org.opensaml.saml.saml1.core.StatusCode; +import org.opensaml.saml.saml1.core.StatusMessage; +import org.opensaml.saml.saml1.core.Subject; +import org.opensaml.saml.saml1.core.SubjectConfirmation; + + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.namespace.QName; +import java.util.Collection; +import java.util.Date; +import java.util.Map; + +/** + * This is the response builder for Saml1 Protocol. + * + * @author Misagh Moayyed mmoayyed@unicon.net + * @since 4.1 + */ +public final class Saml10ObjectBuilder extends AbstractSamlObjectBuilder { + + private static final String CONFIRMATION_METHOD = "urn:oasis:names:tc:SAML:1.0:cm:artifact"; + + /** + * Create a new SAML response object. + * @param id the id + * @param issueInstant the issue instant + * @param recipient the recipient + * @param service the service + * @return the response + */ + public Response newResponse(final String id, final DateTime issueInstant, + final String recipient, final WebApplicationService service) { + + final Response samlResponse = newSamlObject(Response.class); + samlResponse.setID(id); + samlResponse.setIssueInstant(issueInstant); + samlResponse.setVersion(SAMLVersion.VERSION_11); + samlResponse.setInResponseTo(recipient); + if (service instanceof SamlService) { + final SamlService samlService = (SamlService) service; + + if (samlService.getRequestID() != null) { + samlResponse.setInResponseTo(samlService.getRequestID()); + } + } + return samlResponse; + } + + /** + * Create a new SAML1 response object. + * + * @param authnStatement the authn statement + * @param issuer the issuer + * @param issuedAt the issued at + * @param id the id + * @return the assertion + */ + public Assertion newAssertion(final AuthenticationStatement authnStatement, final String issuer, + final DateTime issuedAt, final String id) { + final Assertion assertion = newSamlObject(Assertion.class); + + assertion.setID(id); + assertion.setIssueInstant(issuedAt); + assertion.setIssuer(issuer); + assertion.getAuthenticationStatements().add(authnStatement); + return assertion; + } + + /** + * New conditions element. + * + * @param issuedAt the issued at + * @param audienceUri the service id + * @param issueLength the issue length + * @return the conditions + */ + public Conditions newConditions(final DateTime issuedAt, final String audienceUri, final long issueLength) { + final Conditions conditions = newSamlObject(Conditions.class); + conditions.setNotBefore(issuedAt); + conditions.setNotOnOrAfter(issuedAt.plus(issueLength)); + final AudienceRestrictionCondition audienceRestriction = newSamlObject(AudienceRestrictionCondition.class); + final Audience audience = newSamlObject(Audience.class); + audience.setUri(audienceUri); + audienceRestriction.getAudiences().add(audience); + conditions.getAudienceRestrictionConditions().add(audienceRestriction); + return conditions; + } + + /** + * Create a new SAML status object. + * + * @param codeValue the code value + * @param statusMessage the status message + * @return the status + */ + public Status newStatus(final QName codeValue, final String statusMessage) { + final Status status = newSamlObject(Status.class); + final StatusCode code = newSamlObject(StatusCode.class); + code.setValue(codeValue); + status.setStatusCode(code); + if (statusMessage != null) { + final StatusMessage message = newSamlObject(StatusMessage.class); + message.setMessage(statusMessage); + status.setStatusMessage(message); + } + return status; + } + + /** + * New authentication statement. + * + * @param authenticationDate the authentication date + * @param authenticationMethod the authentication method + * @param subjectId the subject id + * @return the authentication statement + */ + public AuthenticationStatement newAuthenticationStatement(final Date authenticationDate, + final String authenticationMethod, + final String subjectId) { + + final AuthenticationStatement authnStatement = newSamlObject(AuthenticationStatement.class); + authnStatement.setAuthenticationInstant(new DateTime(authenticationDate)); + authnStatement.setAuthenticationMethod( + authenticationMethod != null + ? authenticationMethod + : SamlAuthenticationMetaDataPopulator.AUTHN_METHOD_UNSPECIFIED); + authnStatement.setSubject(newSubject(subjectId)); + return authnStatement; + } + + /** + * New subject element that uses the confirmation method + * {@link #CONFIRMATION_METHOD}. + * + * @param identifier the identifier + * @return the subject + */ + public Subject newSubject(final String identifier) { + return newSubject(identifier, CONFIRMATION_METHOD); + } + + /** + * New subject element with given confirmation method. + * + * @param identifier the identifier + * @param confirmationMethod the confirmation method + * @return the subject + */ + public Subject newSubject(final String identifier, final String confirmationMethod) { + final SubjectConfirmation confirmation = newSamlObject(SubjectConfirmation.class); + final ConfirmationMethod method = newSamlObject(ConfirmationMethod.class); + method.setConfirmationMethod(confirmationMethod); + confirmation.getConfirmationMethods().add(method); + final NameIdentifier nameIdentifier = newSamlObject(NameIdentifier.class); + nameIdentifier.setNameIdentifier(identifier); + final Subject subject = newSamlObject(Subject.class); + subject.setNameIdentifier(nameIdentifier); + subject.setSubjectConfirmation(confirmation); + return subject; + } + + /** + * New attribute statement. + * + * @param subject the subject + * @param attributes the attributes + * @param attributeNamespace the attribute namespace + * @return the attribute statement + */ + public AttributeStatement newAttributeStatement(final Subject subject, + final Map attributes, + final String attributeNamespace) { + + final AttributeStatement attrStatement = newSamlObject(AttributeStatement.class); + attrStatement.setSubject(subject); + for (final Map.Entry e : attributes.entrySet()) { + if (e.getValue() instanceof Collection && ((Collection) e.getValue()).isEmpty()) { + //don't add the attribute, it causes a org.opensaml.MalformedException + logger.info("Skipping attribute {} because it does not have any values.", e.getKey()); + continue; + } + final Attribute attribute = newSamlObject(Attribute.class); + attribute.setAttributeName(e.getKey()); + attribute.setAttributeNamespace(attributeNamespace); + if (e.getValue() instanceof Collection) { + final Collection c = (Collection) e.getValue(); + for (final Object value : c) { + attribute.getAttributeValues().add(newAttributeValue(value, AttributeValue.DEFAULT_ELEMENT_NAME)); + } + } else { + attribute.getAttributeValues().add(newAttributeValue(e.getValue(), AttributeValue.DEFAULT_ELEMENT_NAME)); + } + attrStatement.getAttributes().add(attribute); + } + + return attrStatement; + } + + /** + * Encode response and pass it onto the outbound transport. + * Uses {@link CasHTTPSOAP11Encoder} to handle encoding. + * + * @param httpResponse the http response + * @param httpRequest the http request + * @param samlMessage the saml response + * @throws Exception the exception in case encoding fails. + */ + public void encodeSamlResponse(final HttpServletResponse httpResponse, + final HttpServletRequest httpRequest, + final Response samlMessage) throws Exception { + + final HTTPSOAP11Encoder encoder = new CasHTTPSOAP11Encoder(); + final MessageContext context = new MessageContext(); + context.setMessage(samlMessage); + encoder.setHttpServletResponse(httpResponse); + encoder.setMessageContext(context); + encoder.initialize(); + encoder.prepareContext(); + encoder.encode(); + } +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/SamlCompliantUniqueTicketIdGenerator.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/SamlCompliantUniqueTicketIdGenerator.java new file mode 100644 index 000000000000..9a75ca4c0e6e --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/util/SamlCompliantUniqueTicketIdGenerator.java @@ -0,0 +1,101 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.util; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.opensaml.saml.saml1.binding.artifact.SAML1ArtifactType0001; +import org.opensaml.saml.saml2.binding.artifact.SAML2ArtifactType0004; + +/** + * Unique Ticket Id Generator compliant with the SAML 1.1 specification for + * artifacts. This should also be compliant with the SAML 2 specification. + *

+ * Default to SAML 1.1 Compliance. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public final class SamlCompliantUniqueTicketIdGenerator implements UniqueTicketIdGenerator { + + /** Assertion handles are randomly-generated 20-byte identifiers. */ + private static final int ASSERTION_HANDLE_SIZE = 20; + + /** SAML 2 Type 0004 endpoint ID is 0x0001. */ + private static final byte[] ENDPOINT_ID = {0, 1}; + + /** SAML defines the source id as the server name. */ + private final byte[] sourceIdDigest; + + /** Flag to indicate SAML2 compliance. Default is SAML1.1. */ + private boolean saml2compliant; + + /** Random generator to construct the AssertionHandle. */ + private final SecureRandom random; + + /** + * Instantiates a new SAML compliant unique ticket id generator. + * + * @param sourceId the source id + */ + public SamlCompliantUniqueTicketIdGenerator(final String sourceId) { + try { + final MessageDigest messageDigest = MessageDigest.getInstance("SHA"); + messageDigest.update(sourceId.getBytes("8859_1")); + this.sourceIdDigest = messageDigest.digest(); + } catch (final Exception e) { + throw new IllegalStateException("Exception generating digest of source ID.", e); + } + try { + this.random = SecureRandom.getInstance("SHA1PRNG"); + } catch (final NoSuchAlgorithmException e) { + throw new IllegalStateException("Cannot get SHA1PRNG secure random instance."); + } + } + + /** + * {@inheritDoc} + * We ignore prefixes for SAML compliance. + */ + @Override + public String getNewTicketId(final String prefix) { + if (saml2compliant) { + return new SAML2ArtifactType0004(ENDPOINT_ID, newAssertionHandle(), sourceIdDigest).base64Encode(); + } + return new SAML1ArtifactType0001(this.sourceIdDigest, newAssertionHandle()).base64Encode(); + } + + public void setSaml2compliant(final boolean saml2compliant) { + this.saml2compliant = saml2compliant; + } + + /** + * New assertion handle. + * + * @return the byte[] array of size {@link #ASSERTION_HANDLE_SIZE} + */ + private byte[] newAssertionHandle() { + final byte[] handle = new byte[ASSERTION_HANDLE_SIZE]; + this.random.nextBytes(handle); + return handle; + } +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/AbstractMetadataResolverAdapter.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/AbstractMetadataResolverAdapter.java new file mode 100644 index 000000000000..a8990e4466c4 --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/AbstractMetadataResolverAdapter.java @@ -0,0 +1,212 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.saml.web.flow.mdui; + +import net.shibboleth.utilities.java.support.component.ComponentInitializationException; +import net.shibboleth.utilities.java.support.resolver.CriteriaSet; +import org.jasig.cas.support.saml.OpenSamlConfigBean; +import org.opensaml.core.criterion.EntityIdCriterion; +import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver; +import org.opensaml.saml.metadata.resolver.MetadataResolver; +import org.opensaml.saml.metadata.resolver.filter.MetadataFilter; +import org.opensaml.saml.metadata.resolver.filter.impl.MetadataFilterChain; +import org.opensaml.saml.metadata.resolver.impl.DOMMetadataResolver; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.validation.constraints.NotNull; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * This is {@link AbstractMetadataResolverAdapter} that encapsulates + * commons between static and dynamic resolvers. + * + * @author Misagh Moayyed + * @since 4.1.0 + */ +public abstract class AbstractMetadataResolverAdapter implements MetadataResolverAdapter { + /** Logger instance. */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** Metadata resources along with filters to perform validation. */ + @NotNull + protected final Map metadataResources; + + /** Whether the metadata resolver should require valid metadata. Default is true. */ + protected boolean requireValidMetadata = true; + + /** The openSAML config bean. **/ + @Autowired + @NotNull + protected OpenSamlConfigBean configBean; + + private ChainingMetadataResolver metadataResolver; + + private final Object lock = new Object(); + + /** + * Instantiates a new abstract metadata resolver adapter. + */ + public AbstractMetadataResolverAdapter() { + this.metadataResources = new HashMap<>(); + } + + /** + * Instantiates a new static metadata resolver adapter. + * + * @param metadataResources the metadata resources + */ + public AbstractMetadataResolverAdapter(final Map metadataResources) { + this.metadataResources = metadataResources; + } + + public void setRequireValidMetadata(final boolean requireValidMetadata) { + this.requireValidMetadata = requireValidMetadata; + } + + /** + * Retrieve the remote source's input stream to parse data. + * @param resource the resource + * @param entityId the entity id + * @return the input stream + * @throws IOException if stream cannot be read + */ + protected InputStream getResourceInputStream(final Resource resource, final String entityId) throws IOException { + logger.debug("Locating metadata resource from input stream."); + if (!resource.exists() || !resource.isReadable()) { + throw new FileNotFoundException("Resource does not exist or is unreadable"); + } + return resource.getInputStream(); + } + + @Override + public EntityDescriptor getEntityDescriptorForEntityId(final String entityId) { + try { + final CriteriaSet criterions = new CriteriaSet(new EntityIdCriterion(entityId)); + if (this.metadataResolver != null) { + return metadataResolver.resolveSingle(criterions); + } + } catch (final Exception ex) { + throw new RuntimeException(ex.getMessage(), ex); + } + return null; + + } + + /** + * Build metadata resolver aggregate. + * + */ + protected final void buildMetadataResolverAggregate() { + buildMetadataResolverAggregate(null); + } + + /** + * Build metadata resolver aggregate. Loops through metadata resources + * and attempts to resolve the metadata. + * @param entityId the entity id + */ + public final void buildMetadataResolverAggregate(final String entityId) { + try { + final Set> entries = metadataResources.entrySet(); + for (final Map.Entry entry : entries) { + final Resource resource = entry.getKey(); + logger.debug("Loading [{}]", resource.getFilename()); + loadMetadataFromResource(entry.getValue(), resource, entityId); + } + } catch (final Exception ex) { + throw new RuntimeException(ex.getMessage(), ex); + } + } + + /** + * Load metadata from resource. + * + * @param metadataFilter the metadata filter + * @param resource the resource + * @param entityId the entity id + */ + private void loadMetadataFromResource(final MetadataFilter metadataFilter, + final Resource resource, final String entityId) { + + try (final InputStream in = getResourceInputStream(resource, entityId)) { + logger.debug("Parsing [{}]", resource.getFilename()); + final Document document = this.configBean.getParserPool().parse(in); + + final List resolvers = buildSingleMetadataResolver(metadataFilter, resource, document); + this.metadataResolver = new ChainingMetadataResolver(); + synchronized (this.lock) { + this.metadataResolver.setId(ChainingMetadataResolver.class.getCanonicalName()); + this.metadataResolver.setResolvers(resolvers); + logger.info("Collected metadata from [{}] resource(s). Initializing aggregate resolver...", + resolvers.size()); + this.metadataResolver.initialize(); + logger.info("Metadata aggregate initialized successfully.", resolvers.size()); + } + } catch (final Exception e) { + logger.warn("Could not retrieve input stream from resource. Moving on...", e); + } + } + + /** + * Build single metadata resolver. + * + * @param metadataFilterChain the metadata filters chained together + * @param resource the resource + * @param document the xml document to parse + * @return list of resolved metadata from resources. + * @throws IOException the iO exception + */ + private List buildSingleMetadataResolver(final MetadataFilter metadataFilterChain, + final Resource resource, final Document document) throws IOException { + final List resolvers = new ArrayList<>(); + final Element metadataRoot = document.getDocumentElement(); + final DOMMetadataResolver metadataProvider = new DOMMetadataResolver(metadataRoot); + + metadataProvider.setParserPool(this.configBean.getParserPool()); + metadataProvider.setFailFastInitialization(true); + metadataProvider.setRequireValidMetadata(this.requireValidMetadata); + metadataProvider.setId(metadataProvider.getClass().getCanonicalName()); + if (metadataFilterChain != null) { + metadataProvider.setMetadataFilter(metadataFilterChain); + } + logger.debug("Initializing metadata resolver for [{}]", resource.getURL()); + + try { + metadataProvider.initialize(); + } catch (final ComponentInitializationException ex) { + logger.warn("Could not initialize metadata resolver. Resource will be ignored", ex); + } + resolvers.add(metadataProvider); + return resolvers; + } +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/DynamicMetadataResolverAdapter.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/DynamicMetadataResolverAdapter.java new file mode 100644 index 000000000000..62422d2b50ca --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/DynamicMetadataResolverAdapter.java @@ -0,0 +1,70 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.saml.web.flow.mdui; + +import org.opensaml.saml.metadata.resolver.filter.impl.MetadataFilterChain; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.springframework.core.io.Resource; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.util.Map; + +/** + * A metadata adapter {@link DynamicMetadataResolverAdapter} + * that queries a metadata server on demand following + * the metadata query protocol. + * + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class DynamicMetadataResolverAdapter extends AbstractMetadataResolverAdapter { + + /** + * Instantiates a new static metadata resolver adapter. + * + * @param metadataResources the metadata resources + */ + public DynamicMetadataResolverAdapter(final Map metadataResources) { + super(metadataResources); + } + + @Override + public EntityDescriptor getEntityDescriptorForEntityId(final String entityId) { + buildMetadataResolverAggregate(entityId); + return super.getEntityDescriptorForEntityId(entityId); + } + + @Override + protected InputStream getResourceInputStream(final Resource resource, final String entityId) throws IOException { + final String encodedId = URLEncoder.encode(entityId, "UTF-8"); + final URL url = new URL(resource.getURL().toExternalForm().concat(encodedId)); + + final HttpURLConnection httpcon = (HttpURLConnection) (url.openConnection()); + httpcon.setDoOutput(true); + httpcon.addRequestProperty("Accept", "*/*"); + httpcon.setRequestMethod("GET"); + httpcon.connect(); + return httpcon.getInputStream(); + } +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/MetadataResolverAdapter.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/MetadataResolverAdapter.java new file mode 100644 index 000000000000..03f90f9d3da6 --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/MetadataResolverAdapter.java @@ -0,0 +1,39 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.saml.web.flow.mdui; + +import org.opensaml.saml.saml2.metadata.EntityDescriptor; + +/** + * {@link MetadataResolverAdapter} is a facade on top of the existing + * metadata resolution machinery that defines how metadata may be resolved. + * + * @author Misagh Moayyed + * @since 4.1.0 + */ +public interface MetadataResolverAdapter { + /** + * Gets entity descriptor for entity id. + * + * @param entityId the entity id + * @return the entity descriptor for entity id + */ + EntityDescriptor getEntityDescriptorForEntityId(final String entityId); +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/SamlMetadataUIParserAction.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/SamlMetadataUIParserAction.java new file mode 100644 index 000000000000..4a4120792ed8 --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/SamlMetadataUIParserAction.java @@ -0,0 +1,175 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.saml.web.flow.mdui; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.services.UnauthorizedServiceException; +import org.jasig.cas.web.support.WebUtils; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.ext.saml2mdui.UIInfo; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml.saml2.metadata.Extensions; +import org.opensaml.saml.saml2.metadata.SPSSODescriptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.webflow.action.AbstractAction; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * This is {@link SamlMetadataUIParserAction} that attempts to parse + * the mdui extension block for a SAML SP from the provided metadata locations. + * The result is put into the flow request context under the parameter + * {@link #MDUI_FLOW_PARAMETER_NAME}. The entity id parameter is + * specified by default at {@link #ENTITY_ID_PARAMETER_NAME}. + * + *

This action is best suited to be invoked when the CAS login page + * is about to render so that the page, once the MDUI info is obtained, + * has a chance to populate the UI with relevant info about the SP.

+ * + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class SamlMetadataUIParserAction extends AbstractAction { + /** + * The default entityId parameter name. + */ + public static final String ENTITY_ID_PARAMETER_NAME = "entityId"; + + /** + * The default entityId parameter name. + */ + public static final String MDUI_FLOW_PARAMETER_NAME = "mduiContext"; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @NotNull + private final String entityIdParameterName; + + @NotNull + private final MetadataResolverAdapter metadataAdapter; + + @Autowired + @NotNull + private ServicesManager servicesManager; + + /** + * Instantiates a new SAML mdui parser action. + * Defaults the parameter name to {@link #ENTITY_ID_PARAMETER_NAME}. + * + * @param metadataAdapter the metadata resources + */ + public SamlMetadataUIParserAction(final MetadataResolverAdapter metadataAdapter) { + this(ENTITY_ID_PARAMETER_NAME, metadataAdapter); + } + + /** + * Instantiates a new SAML mdui parser action. + * + * @param entityIdParameterName the entity id parameter name + * @param metadataAdapter the metadata adapter + */ + public SamlMetadataUIParserAction(final String entityIdParameterName, + final MetadataResolverAdapter metadataAdapter) { + this.entityIdParameterName = entityIdParameterName; + this.metadataAdapter = metadataAdapter; + } + + @Override + protected Event doExecute(final RequestContext requestContext) throws Exception { + final HttpServletRequest request = WebUtils.getHttpServletRequest(requestContext); + final String entityId = request.getParameter(this.entityIdParameterName); + if (StringUtils.isBlank(entityId)) { + logger.debug("No entity id found for parameter [{}]", this.entityIdParameterName); + return success(); + } + + final WebApplicationService service = new SimpleWebApplicationServiceImpl(entityId); + final RegisteredService registeredService = this.servicesManager.findServiceBy(service); + if (registeredService == null || !registeredService.getAccessStrategy().isServiceAccessAllowed()) { + logger.debug("Entity id [{}] is not recognized/allowed by the CAS service registry", entityId); + throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE, + "Entity " + entityId + " not recognized"); + } + + final EntityDescriptor entityDescriptor = this.metadataAdapter.getEntityDescriptorForEntityId(entityId); + if (entityDescriptor == null) { + logger.debug("Entity descriptor not found for [{}]", entityId); + return success(); + } + + final SPSSODescriptor spssoDescriptor = getSPSSODescriptor(entityDescriptor); + if (spssoDescriptor == null) { + logger.debug("SP SSO descriptor not found for [{}]", entityId); + return success(); + } + + final Extensions extensions = spssoDescriptor.getExtensions(); + final List spExtensions = extensions.getUnknownXMLObjects(UIInfo.DEFAULT_ELEMENT_NAME); + if (spExtensions.isEmpty()) { + logger.debug("No extensions are found for [{}]", UIInfo.DEFAULT_ELEMENT_NAME.getNamespaceURI()); + return success(); + } + + final SimpleMetadataUIInfo mdui = new SimpleMetadataUIInfo(registeredService); + + for (final XMLObject obj : spExtensions) { + if (obj instanceof UIInfo) { + final UIInfo uiInfo = (UIInfo) obj; + logger.debug("Found UI info for [{}] and added to flow context", entityId); + mdui.setUIInfo(uiInfo); + } + } + + requestContext.getFlowScope().put(MDUI_FLOW_PARAMETER_NAME, mdui); + return success(); + } + + /** + * Gets SP SSO descriptor. + * + * @param entityDescriptor the entity descriptor + * @return the sPSSO descriptor + */ + private SPSSODescriptor getSPSSODescriptor(final EntityDescriptor entityDescriptor) { + logger.debug("Locating SP SSO descriptor for SAML2 protocol..."); + SPSSODescriptor spssoDescriptor = entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML20P_NS); + if (spssoDescriptor == null) { + logger.debug("Locating SP SSO descriptor for SAML11 protocol..."); + spssoDescriptor = entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML11P_NS); + } + if (spssoDescriptor == null) { + logger.debug("Locating SP SSO descriptor for SAML1 protocol..."); + spssoDescriptor = entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML10P_NS); + } + logger.debug("SP SSO descriptor resolved to be [{}]", spssoDescriptor); + return spssoDescriptor; + } +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/SimpleMetadataUIInfo.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/SimpleMetadataUIInfo.java new file mode 100644 index 000000000000..4ac7cf4ae5e3 --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/SimpleMetadataUIInfo.java @@ -0,0 +1,223 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.saml.web.flow.mdui; + +import org.jasig.cas.services.RegisteredService; +import org.opensaml.core.xml.schema.XSString; +import org.opensaml.core.xml.schema.XSURI; +import org.opensaml.saml.ext.saml2mdui.Logo; +import org.opensaml.saml.ext.saml2mdui.UIInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import javax.annotation.Nullable; +import javax.validation.constraints.NotNull; +import java.io.Serializable; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * This is {@link SimpleMetadataUIInfo}. + * + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class SimpleMetadataUIInfo implements Serializable { + private static final Logger LOGGER = LoggerFactory.getLogger(SimpleMetadataUIInfo.class); + + private static final long serialVersionUID = -1434801982864628179L; + + private transient UIInfo uiInfo; + + private final transient RegisteredService registeredService; + + /** + * Instantiates a new Simple metadata uI info. + * + * @param registeredService the registered service + */ + public SimpleMetadataUIInfo(final RegisteredService registeredService) { + this(null, registeredService); + } + + /** + * Instantiates a new Simple mdui info. + * + * @param uiInfo the ui info + * @param registeredService the registered service + */ + public SimpleMetadataUIInfo(@Nullable final UIInfo uiInfo, final RegisteredService registeredService) { + this.uiInfo = uiInfo; + this.registeredService = registeredService; + } + + /** + * Gets description. + * + * @return the description + */ + public String getDescription() { + final Collection items = getDescriptions(); + if (items.isEmpty()) { + return this.registeredService.getDescription(); + } + return StringUtils.collectionToDelimitedString(items, "."); + } + + /** + * Gets descriptions. + * + * @return the descriptions + */ + public Collection getDescriptions() { + if (uiInfo != null) { + return getStringValues(uiInfo.getDescriptions()); + } + return new ArrayList<>(); + } + + /** + * Gets display name. + * + * @return the display name + */ + public String getDisplayName() { + final Collection items = getDisplayNames(); + if (items.isEmpty()) { + return this.registeredService.getName(); + } + return StringUtils.collectionToDelimitedString(items, "."); + } + + /** + * Gets display names. + * + * @return the display names + */ + public Collection getDisplayNames() { + if (uiInfo != null) { + return getStringValues(uiInfo.getDisplayNames()); + } + return new ArrayList<>(); + } + + /** + * Gets information uRL. + * + * @return the information uRL + */ + public String getInformationURL() { + final Collection items = getInformationURLs(); + return StringUtils.collectionToDelimitedString(items, "."); + } + + /** + * Gets information uR ls. + * + * @return the information uR ls + */ + public Collection getInformationURLs() { + if (uiInfo != null) { + return getStringValues(uiInfo.getInformationURLs()); + } + return new ArrayList<>(); + } + + /** + * Gets privacy statement uRL. + * + * @return the privacy statement uRL + */ + public String getPrivacyStatementURL() { + final Collection items = getPrivacyStatementURLs(); + return StringUtils.collectionToDelimitedString(items, "."); + } + + /** + * Gets privacy statement uR ls. + * + * @return the privacy statement uR ls + */ + public Collection getPrivacyStatementURLs() { + if (uiInfo != null) { + return getStringValues(uiInfo.getPrivacyStatementURLs()); + } + return new ArrayList<>(); + } + + /** + * Gets logo url. + * + * @return the logo url + */ + public URL getLogoUrl() { + try { + final Collection items = getLogoUrls(); + if (!items.isEmpty()) { + return new URL(items.iterator().next().getURL()); + } + } catch (final Exception e) { + LOGGER.debug(e.getMessage(), e); + } + return this.registeredService.getLogo(); + } + + /** + * Gets logo urls. + * + * @return the logo urls + */ + public Collection getLogoUrls() { + final List list = new ArrayList<>(); + + if (uiInfo != null) { + for (final Logo d : uiInfo.getLogos()) { + list.add(d); + } + } + + return list; + } + + /** + * Gets string values from the list of mdui objects. + * + * @param items the items + * @return the string values + */ + private Collection getStringValues(final List items) { + final List list = new ArrayList<>(); + for (final Object d : items) { + if (d instanceof XSURI) { + list.add(((XSURI) d).getValue()); + } else if (d instanceof XSString) { + list.add(((XSString) d).getValue()); + } + } + return list; + } + + public void setUIInfo(@NotNull final UIInfo uiInfo) { + this.uiInfo = uiInfo; + } +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/StaticMetadataResolverAdapter.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/StaticMetadataResolverAdapter.java new file mode 100644 index 000000000000..27f8f6fa072c --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/flow/mdui/StaticMetadataResolverAdapter.java @@ -0,0 +1,107 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.saml.web.flow.mdui; + +import org.opensaml.saml.metadata.resolver.filter.impl.MetadataFilterChain; +import org.quartz.Job; +import org.quartz.JobBuilder; +import org.quartz.JobDetail; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.SchedulerFactory; +import org.quartz.SimpleScheduleBuilder; +import org.quartz.Trigger; +import org.quartz.TriggerBuilder; +import org.quartz.impl.StdSchedulerFactory; +import org.springframework.core.io.Resource; + +import javax.annotation.PostConstruct; +import java.util.Map; + +/** + * A {@link StaticMetadataResolverAdapter} that loads metadata from static xml files + * served by urls or locally. + * + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class StaticMetadataResolverAdapter extends AbstractMetadataResolverAdapter implements Job { + private static final int DEFAULT_METADATA_REFRESH_INTERNAL_MINS = 300; + + /** + * Refresh metadata every {@link #DEFAULT_METADATA_REFRESH_INTERNAL_MINS} + * minutes by default. + **/ + private int refreshIntervalInMinutes = DEFAULT_METADATA_REFRESH_INTERNAL_MINS; + + /** + * New ctor - required for serialization and job scheduling. + */ + public StaticMetadataResolverAdapter() { + super(); + } + + /** + * Instantiates a new static metadata resolver adapter. + * + * @param metadataResources the metadata resources + */ + public StaticMetadataResolverAdapter(final Map metadataResources) { + super(metadataResources); + } + + public void setRefreshIntervalInMinutes(final int refreshIntervalInMinutes) { + this.refreshIntervalInMinutes = refreshIntervalInMinutes; + } + + /** + * Refresh metadata. Schedules the job to retrieve metadata. + * @throws SchedulerException the scheduler exception + */ + @PostConstruct + public void refreshMetadata() throws SchedulerException { + final Thread thread = new Thread(new Runnable() { + @Override + public void run() { + buildMetadataResolverAggregate(); + } + }); + thread.start(); + + final JobDetail job = JobBuilder.newJob(this.getClass()) + .withIdentity(this.getClass().getSimpleName()).build(); + final Trigger trigger = TriggerBuilder.newTrigger() + .withSchedule(SimpleScheduleBuilder.simpleSchedule() + .withIntervalInMinutes(this.refreshIntervalInMinutes) + .repeatForever()).build(); + + final SchedulerFactory schFactory = new StdSchedulerFactory(); + final Scheduler sch = schFactory.getScheduler(); + sch.start(); + sch.scheduleJob(job, trigger); + } + + @Override + public void execute(final JobExecutionContext jobExecutionContext) throws JobExecutionException { + buildMetadataResolverAggregate(); + } +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/support/GoogleAccountsArgumentExtractor.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/support/GoogleAccountsArgumentExtractor.java new file mode 100644 index 000000000000..f7eb70cec892 --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/support/GoogleAccountsArgumentExtractor.java @@ -0,0 +1,104 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.web.support; + +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.support.saml.authentication.principal.GoogleAccountsService; +import org.jasig.cas.web.support.AbstractArgumentExtractor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.constraints.NotNull; +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * Constructs a GoogleAccounts compatible service and provides the public and + * private keys. + * + * @author Scott Battaglia + * @since 3.1 + */ +public final class GoogleAccountsArgumentExtractor extends AbstractArgumentExtractor { + + private static final Logger LOGGER = LoggerFactory.getLogger(GoogleAccountsArgumentExtractor.class); + + @NotNull + private final PublicKey publicKey; + + @NotNull + private final PrivateKey privateKey; + + @NotNull + private final ServicesManager servicesManager; + + /** + * Instantiates a new google accounts argument extractor. + * + * @param publicKey the public key + * @param privateKey the private key + * @param servicesManager the services manager + */ + public GoogleAccountsArgumentExtractor(final PublicKey publicKey, + final PrivateKey privateKey, final ServicesManager servicesManager) { + this.publicKey = publicKey; + this.privateKey = privateKey; + this.servicesManager = servicesManager; + } + + @Override + public WebApplicationService extractServiceInternal(final HttpServletRequest request) { + return GoogleAccountsService.createServiceFrom(request, + this.privateKey, this.publicKey, this.servicesManager); + } + + /** + * @deprecated As of 4.1. Use Ctors instead. + * @param privateKey the private key object + */ + @Deprecated + public void setPrivateKey(final PrivateKey privateKey) { + LOGGER.warn("setPrivateKey() is deprecated and has no effect. Consider using constructors instead."); + } + + /** + * @deprecated As of 4.1. Use Ctors instead. + * @param publicKey the public key object + */ + @Deprecated + public void setPublicKey(final PublicKey publicKey) { + LOGGER.warn("setPublicKey() is deprecated and has no effect. Consider using constructors instead."); + } + + /** + * @deprecated As of 4.1. The behavior is controlled by the service registry instead. + * Sets an alternate username to send to Google (i.e. fully qualified email address). Relies on an appropriate + * attribute available for the user. + *

+ * Note that this is optional and the default is to use the normal identifier. + * + * @param alternateUsername the alternate username. This is OPTIONAL. + */ + @Deprecated + public void setAlternateUsername(final String alternateUsername) { + LOGGER.warn("setAlternateUsername() is deprecated and has no effect. Instead use the configuration in service registry."); + } +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/support/SamlArgumentExtractor.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/support/SamlArgumentExtractor.java new file mode 100644 index 000000000000..f71d38c3b8d1 --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/support/SamlArgumentExtractor.java @@ -0,0 +1,39 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.web.support; + +import javax.servlet.http.HttpServletRequest; + +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.support.saml.authentication.principal.SamlService; +import org.jasig.cas.web.support.AbstractArgumentExtractor; + +/** + * Retrieve the ticket and artifact based on the SAML 1.1 profile. + * + * @author Scott Battaglia + * @since 3.1 + */ +public final class SamlArgumentExtractor extends AbstractArgumentExtractor { + + @Override + public WebApplicationService extractServiceInternal(final HttpServletRequest request) { + return SamlService.createServiceFrom(request); + } +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/view/AbstractSaml10ResponseView.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/view/AbstractSaml10ResponseView.java new file mode 100644 index 000000000000..06e3bb533475 --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/view/AbstractSaml10ResponseView.java @@ -0,0 +1,122 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.web.view; + +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.support.saml.util.Saml10ObjectBuilder; +import org.jasig.cas.support.saml.web.support.SamlArgumentExtractor; +import org.jasig.cas.web.view.AbstractCasView; +import org.joda.time.DateTime; + +import org.opensaml.saml.saml1.core.Response; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; +import java.util.Map; + +/** + * Base class for all views that render SAML1 SOAP messages directly to the HTTP response stream. + * + * @author Marvin S. Addison + * @since 3.5.1 + */ +public abstract class AbstractSaml10ResponseView extends AbstractCasView { + private static final String DEFAULT_ENCODING = "UTF-8"; + + /** + * The Saml object builder. + */ + protected final Saml10ObjectBuilder samlObjectBuilder = new Saml10ObjectBuilder(); + + private final SamlArgumentExtractor samlArgumentExtractor = new SamlArgumentExtractor(); + + @NotNull + private String encoding = DEFAULT_ENCODING; + + /** Defaults to 0. */ + private int skewAllowance; + + /** + * Sets the character encoding in the HTTP response. + * + * @param encoding Response character encoding. + */ + public void setEncoding(final String encoding) { + this.encoding = encoding; + } + + /** + * Sets the allowance for time skew in seconds + * between CAS and the client server. Default 0s. + * This value will be subtracted from the current time when setting the SAML + * NotBeforeDate attribute, thereby allowing for the + * CAS server to be ahead of the client by as much as the value defined here. + * + *

Note: Skewing of the issue instant via setting this property + * applies to all saml assertions that are issued by CAS and it + * currently cannot be controlled on a per relying party basis. + * Before configuring this, it is recommended that each service provider + * attempt to correctly sync their system time with an NTP server + * so as to match the CAS server's issue instant config and to + * avoid applying this setting globally. This should only + * be used in situations where the NTP server is unresponsive to + * sync time on the client, or the client is simply unable + * to adjust their server time configuration.

+ * + * @param skewAllowance Number of seconds to allow for variance. + */ + public void setSkewAllowance(final int skewAllowance) { + logger.debug("Using {} seconds as skew allowance.", skewAllowance); + this.skewAllowance = skewAllowance; + } + + @Override + protected void renderMergedOutputModel( + final Map model, final HttpServletRequest request, final HttpServletResponse response) throws Exception { + + response.setCharacterEncoding(this.encoding); + + final WebApplicationService service = this.samlArgumentExtractor.extractService(request); + final String serviceId = service != null ? service.getId() : "UNKNOWN"; + + try { + final Response samlResponse = this.samlObjectBuilder.newResponse( + this.samlObjectBuilder.generateSecureRandomId(), + DateTime.now().minusSeconds(this.skewAllowance), serviceId, service); + + prepareResponse(samlResponse, model); + + this.samlObjectBuilder.encodeSamlResponse(response, request, samlResponse); + } catch (final Exception e) { + logger.error("Error generating SAML response for service {}.", serviceId); + throw e; + } + } + + /** + * Subclasses must implement this method by adding child elements (status, assertion, etc) to + * the given empty SAML 1 response message. Impelmenters need not be concerned with error handling. + * + * @param response SAML 1 response message to be filled. + * @param model Spring MVC model map containing data needed to prepare response. + */ + protected abstract void prepareResponse(Response response, Map model); + +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/view/Saml10FailureResponseView.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/view/Saml10FailureResponseView.java new file mode 100644 index 000000000000..09cb5d298ed1 --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/view/Saml10FailureResponseView.java @@ -0,0 +1,40 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.web.view; + + +import org.opensaml.saml.saml1.core.Response; +import org.opensaml.saml.saml1.core.StatusCode; + +import java.util.Map; + +/** + * Represents a failed attempt at validating a ticket, responding via a SAML SOAP message. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.1 + */ +public final class Saml10FailureResponseView extends AbstractSaml10ResponseView { + + @Override + protected void prepareResponse(final Response response, final Map model) { + response.setStatus(this.samlObjectBuilder.newStatus(StatusCode.REQUEST_DENIED, (String) model.get("description"))); + } +} diff --git a/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/view/Saml10SuccessResponseView.java b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/view/Saml10SuccessResponseView.java new file mode 100644 index 000000000000..00c3d21d8b42 --- /dev/null +++ b/cas-server-support-saml/src/main/java/org/jasig/cas/support/saml/web/view/Saml10SuccessResponseView.java @@ -0,0 +1,138 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.web.view; + +import org.jasig.cas.CasProtocolConstants; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.RememberMeCredential; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.support.saml.authentication.SamlAuthenticationMetaDataPopulator; +import org.joda.time.DateTime; +import org.opensaml.saml.saml1.core.Assertion; +import org.opensaml.saml.saml1.core.AuthenticationStatement; +import org.opensaml.saml.saml1.core.Conditions; +import org.opensaml.saml.saml1.core.Response; +import org.opensaml.saml.saml1.core.StatusCode; +import org.opensaml.saml.saml1.core.Subject; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import java.util.HashMap; +import java.util.Map; + +/** + * Implementation of a view to return a SAML SOAP response and assertion, based on + * the SAML 1.1 specification. + *

+ * If an AttributePrincipal is supplied, then the assertion will include the + * attributes from it (assuming a String key/Object value pair). The only + * Authentication attribute it will look at is the authMethod (if supplied). + *

+ * Note that this class will currently not handle proxy authentication. + *

+ * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.1 + */ +public final class Saml10SuccessResponseView extends AbstractSaml10ResponseView { + /** Namespace for custom attributes in the saml validation payload. */ + private static final String VALIDATION_SAML_ATTRIBUTE_NAMESPACE = "http://www.ja-sig.org/products/cas/"; + + private static final int DEFAULT_ISSUE_LENGTH = 30000; + + /** The issuer, generally the hostname. */ + @NotNull + private String issuer; + + /** + * The amount of time in milliseconds this is valid for. + * Defaults to {@value}. + **/ + @Min(1000) + private long issueLength = DEFAULT_ISSUE_LENGTH; + + @NotNull + private String rememberMeAttributeName = CasProtocolConstants.VALIDATION_REMEMBER_ME_ATTRIBUTE_NAME; + + @Override + protected void prepareResponse(final Response response, final Map model) { + + final DateTime issuedAt = response.getIssueInstant(); + final Service service = getAssertionFrom(model).getService(); + + final Authentication authentication = getPrimaryAuthenticationFrom(model); + final String authenticationMethod = (String) authentication.getAttributes().get( + SamlAuthenticationMetaDataPopulator.ATTRIBUTE_AUTHENTICATION_METHOD); + + final AuthenticationStatement authnStatement = this.samlObjectBuilder.newAuthenticationStatement( + authentication.getAuthenticationDate(), authenticationMethod, getPrincipal(model).getId()); + + final Assertion assertion = this.samlObjectBuilder.newAssertion(authnStatement, this.issuer, issuedAt, + this.samlObjectBuilder.generateSecureRandomId()); + final Conditions conditions = this.samlObjectBuilder.newConditions(issuedAt, service.getId(), this.issueLength); + assertion.setConditions(conditions); + + final Subject subject = this.samlObjectBuilder.newSubject(getPrincipal(model).getId()); + final Map attributesToSend = prepareSamlAttributes(model); + + if (!attributesToSend.isEmpty()) { + assertion.getAttributeStatements().add(this.samlObjectBuilder.newAttributeStatement( + subject, attributesToSend, VALIDATION_SAML_ATTRIBUTE_NAMESPACE)); + } + + response.setStatus(this.samlObjectBuilder.newStatus(StatusCode.SUCCESS, null)); + response.getAssertions().add(assertion); + } + + + /** + * Prepare saml attributes. Combines both principal and authentication + * attributes. If the authentication is to be remembered, uses {@link #setRememberMeAttributeName(String)} + * for the remember-me attribute name. + * + * @param model the model + * @return the final map + * @since 4.1.0 + */ + private Map prepareSamlAttributes(final Map model) { + final Map authnAttributes = + new HashMap<>(getAuthenticationAttributesAsMultiValuedAttributes(model)); + if (isRememberMeAuthentication(model)) { + authnAttributes.remove(RememberMeCredential.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME); + authnAttributes.put(this.rememberMeAttributeName, Boolean.TRUE.toString()); + } + final Map attributesToReturn = new HashMap<>(); + attributesToReturn.putAll(getPrincipalAttributesAsMultiValuedAttributes(model)); + attributesToReturn.putAll(authnAttributes); + return attributesToReturn; + } + + public void setIssueLength(final long issueLength) { + this.issueLength = issueLength; + } + + public void setIssuer(final String issuer) { + this.issuer = issuer; + } + + public void setRememberMeAttributeName(final String rememberMeAttributeName) { + this.rememberMeAttributeName = rememberMeAttributeName; + } +} diff --git a/cas-server-support-saml/src/main/resources/META-INF/spring/opensaml-config.xml b/cas-server-support-saml/src/main/resources/META-INF/spring/opensaml-config.xml new file mode 100644 index 000000000000..682b229b582d --- /dev/null +++ b/cas-server-support-saml/src/main/resources/META-INF/spring/opensaml-config.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-support-saml/src/main/resources/META-INF/spring/saml-protocol-views.xml b/cas-server-support-saml/src/main/resources/META-INF/spring/saml-protocol-views.xml new file mode 100644 index 000000000000..6152369bd471 --- /dev/null +++ b/cas-server-support-saml/src/main/resources/META-INF/spring/saml-protocol-views.xml @@ -0,0 +1,48 @@ + + + + This file is loaded by the Spring configuration automatically, and serves as a placeholder + for various view definitions and beans. This helps with construction of views that require + references to other beans and whose changes can be configured externally. + + + + + + + + + + + diff --git a/cas-server-support-saml/src/site/site.xml b/cas-server-support-saml/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-support-saml/src/site/site.xml @@ -0,0 +1,28 @@ + + + + +

+ + + + diff --git a/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/AbstractOpenSamlTests.java b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/AbstractOpenSamlTests.java new file mode 100644 index 000000000000..27ab6fe2e8bf --- /dev/null +++ b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/AbstractOpenSamlTests.java @@ -0,0 +1,93 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.saml; + +import net.shibboleth.utilities.java.support.xml.ParserPool; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.opensaml.core.xml.XMLObjectBuilderFactory; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.core.xml.io.MarshallerFactory; +import org.opensaml.core.xml.io.UnmarshallerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +import static org.junit.Assert.*; + +/** + * OpenSaml context loading tests. + * @author Misagh Moayyed + * @since 4.1 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration({"/META-INF/spring/opensaml-config.xml"}) +@WebAppConfiguration() +public abstract class AbstractOpenSamlTests { + @Autowired + protected ApplicationContext applicationContext; + + @Autowired + protected OpenSamlConfigBean configBean; + + @Autowired + protected ParserPool parserPool; + + @Autowired + @Qualifier("shibboleth.BuilderFactory") + protected XMLObjectBuilderFactory builderFactory; + + @Autowired + @Qualifier("shibboleth.MarshallerFactory") + protected MarshallerFactory marshallerFactory; + + @Autowired + @Qualifier("shibboleth.UnmarshallerFactory") + protected UnmarshallerFactory unmarshallerFactory; + + @Test + public void autowireApplicationContext() { + assertNotNull(this.applicationContext); + assertNotNull(this.configBean); + assertNotNull(this.parserPool); + assertNotNull(this.builderFactory); + assertNotNull(this.unmarshallerFactory); + assertNotNull(this.marshallerFactory); + assertNotNull(this.configBean.getParserPool()); + } + + @Test + public void loadStaticContextFactories() { + assertNotNull(XMLObjectProviderRegistrySupport.getParserPool()); + assertNotNull(XMLObjectProviderRegistrySupport.getBuilderFactory()); + assertNotNull(XMLObjectProviderRegistrySupport.getMarshallerFactory()); + assertNotNull(XMLObjectProviderRegistrySupport.getUnmarshallerFactory()); + } + + + @Test + public void ensureParserIsInitialized() throws Exception { + assertNotNull(this.parserPool); + assertNotNull(this.parserPool.getBuilder()); + } +} diff --git a/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/authentication/SamlAuthenticationMetaDataPopulatorTests.java b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/authentication/SamlAuthenticationMetaDataPopulatorTests.java new file mode 100644 index 000000000000..077991b11921 --- /dev/null +++ b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/authentication/SamlAuthenticationMetaDataPopulatorTests.java @@ -0,0 +1,110 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.authentication; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.AuthenticationBuilder; +import org.jasig.cas.authentication.DefaultAuthenticationBuilder; +import org.jasig.cas.authentication.AuthenticationHandler; +import org.jasig.cas.authentication.BasicCredentialMetaData; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.CredentialMetaData; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler; +import org.jasig.cas.authentication.principal.Principal; +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * @author Scott Battaglia + * @since 3.1 + * + */ +public class SamlAuthenticationMetaDataPopulatorTests { + + private SamlAuthenticationMetaDataPopulator populator; + + @Before + public void setUp() throws Exception { + this.populator = new SamlAuthenticationMetaDataPopulator(); + } + + @Test + public void verifyAuthenticationTypeFound() { + final UsernamePasswordCredential credentials = new UsernamePasswordCredential(); + final AuthenticationBuilder builder = newAuthenticationBuilder(TestUtils.getPrincipal()); + this.populator.populateAttributes(builder, credentials); + final Authentication auth = builder.build(); + + assertEquals( + auth.getAttributes().get(SamlAuthenticationMetaDataPopulator.ATTRIBUTE_AUTHENTICATION_METHOD), + SamlAuthenticationMetaDataPopulator.AUTHN_METHOD_PASSWORD); + } + + @Test + public void verifyAuthenticationTypeNotFound() { + final CustomCredential credentials = new CustomCredential(); + final AuthenticationBuilder builder = newAuthenticationBuilder(TestUtils.getPrincipal()); + this.populator.populateAttributes(builder, credentials); + final Authentication auth = builder.build(); + + assertNull(auth.getAttributes().get(SamlAuthenticationMetaDataPopulator.ATTRIBUTE_AUTHENTICATION_METHOD)); + } + + @Test + public void verifyAuthenticationTypeFoundCustom() { + final CustomCredential credentials = new CustomCredential(); + + final Map added = new HashMap<>(); + added.put(CustomCredential.class.getName(), "FF"); + + this.populator.setUserDefinedMappings(added); + + final AuthenticationBuilder builder = newAuthenticationBuilder(TestUtils.getPrincipal()); + this.populator.populateAttributes(builder, credentials); + final Authentication auth = builder.build(); + + assertEquals( + "FF", + auth.getAttributes().get(SamlAuthenticationMetaDataPopulator.ATTRIBUTE_AUTHENTICATION_METHOD)); + } + + private static class CustomCredential implements Credential { + + public String getId() { + return "nobody"; + } + } + + private static AuthenticationBuilder newAuthenticationBuilder(final Principal principal) { + final CredentialMetaData meta = new BasicCredentialMetaData(new UsernamePasswordCredential()); + final AuthenticationHandler handler = new SimpleTestUsernamePasswordAuthenticationHandler(); + return new DefaultAuthenticationBuilder(principal) + .addCredential(meta) + .addSuccess("test", new DefaultHandlerResult(handler, meta)); + } +} diff --git a/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/authentication/SamlAuthenticationRequestTests.java b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/authentication/SamlAuthenticationRequestTests.java new file mode 100644 index 000000000000..85e24af10ec7 --- /dev/null +++ b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/authentication/SamlAuthenticationRequestTests.java @@ -0,0 +1,71 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.saml.authentication; + +import org.jasig.cas.support.saml.AbstractOpenSamlTests; +import org.jasig.cas.support.saml.util.AbstractSaml20ObjectBuilder; +import org.jasig.cas.support.saml.util.GoogleSaml20ObjectBuilder; +import org.jasig.cas.util.CompressionUtils; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.DeflaterOutputStream; + +import static org.junit.Assert.*; + +/** + * Utility class to ensure authentication requests are properly encoded and decoded. + * @author Misagh Moayyed + * @since 4.1 + */ +public class SamlAuthenticationRequestTests extends AbstractOpenSamlTests { + private static final String SAML_REQUEST = "" + + ""; + + @Test + public void ensureDeflation() throws Exception { + final String deflator = CompressionUtils.deflate(SAML_REQUEST); + final String deflatorStream = deflateViaStream(SAML_REQUEST); + assertEquals(deflatorStream, deflator); + } + + @Test + public void ensureInflation() throws Exception { + final String deflator = CompressionUtils.deflate(SAML_REQUEST); + final AbstractSaml20ObjectBuilder builder = new GoogleSaml20ObjectBuilder(); + final String msg = builder.decodeSamlAuthnRequest(deflator); + assertEquals(msg, SAML_REQUEST); + } + + private String deflateViaStream(final String samlRequest) throws IOException { + final byte[] xmlBytes = samlRequest.getBytes("UTF-8"); + final ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); + final DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream( + byteOutputStream); + deflaterOutputStream.write(xmlBytes, 0, xmlBytes.length); + deflaterOutputStream.close(); + return CompressionUtils.encodeBase64(byteOutputStream.toByteArray()); + } +} diff --git a/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/authentication/principal/GoogleAccountsServiceTests.java b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/authentication/principal/GoogleAccountsServiceTests.java new file mode 100644 index 000000000000..c14ad889ed64 --- /dev/null +++ b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/authentication/principal/GoogleAccountsServiceTests.java @@ -0,0 +1,110 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.authentication.principal; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.principal.DefaultResponse; +import org.jasig.cas.authentication.principal.Response; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.services.DefaultRegisteredServiceUsernameProvider; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.support.saml.AbstractOpenSamlTests; +import org.jasig.cas.support.saml.SamlProtocolConstants; +import org.jasig.cas.util.CompressionUtils; +import org.jasig.cas.util.PrivateKeyFactoryBean; +import org.jasig.cas.util.PublicKeyFactoryBean; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.io.IOException; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; + +import static org.mockito.Mockito.*; +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @since 3.1 + */ +public class GoogleAccountsServiceTests extends AbstractOpenSamlTests { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private GoogleAccountsService googleAccountsService; + + public static GoogleAccountsService getGoogleAccountsService() throws Exception { + final PublicKeyFactoryBean pubKeyFactoryBean = new PublicKeyFactoryBean(); + pubKeyFactoryBean.setAlgorithm("DSA"); + final PrivateKeyFactoryBean privKeyFactoryBean = new PrivateKeyFactoryBean(); + privKeyFactoryBean.setAlgorithm("DSA"); + + final ClassPathResource pubKeyResource = new ClassPathResource("DSAPublicKey01.key"); + final ClassPathResource privKeyResource = new ClassPathResource("DSAPrivateKey01.key"); + + pubKeyFactoryBean.setLocation(pubKeyResource); + privKeyFactoryBean.setLocation(privKeyResource); + pubKeyFactoryBean.afterPropertiesSet(); + privKeyFactoryBean.afterPropertiesSet(); + + final DSAPrivateKey privateKey = (DSAPrivateKey) privKeyFactoryBean.getObject(); + final DSAPublicKey publicKey = (DSAPublicKey) pubKeyFactoryBean.getObject(); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + + final String samlRequest = "" + + ""; + request.setParameter(SamlProtocolConstants.PARAMETER_SAML_REQUEST, encodeMessage(samlRequest)); + request.setParameter(SamlProtocolConstants.PARAMETER_SAML_RELAY_STATE, "RelayStateAddedHere"); + + final RegisteredService regSvc = mock(RegisteredService.class); + when(regSvc.getUsernameAttributeProvider()).thenReturn(new DefaultRegisteredServiceUsernameProvider()); + + final ServicesManager servicesManager = mock(ServicesManager.class); + when(servicesManager.findServiceBy(any(Service.class))).thenReturn(regSvc); + + return GoogleAccountsService.createServiceFrom(request, privateKey, publicKey, servicesManager); + } + + @Before + public void setUp() throws Exception { + this.googleAccountsService = getGoogleAccountsService(); + this.googleAccountsService.setPrincipal(TestUtils.getPrincipal()); + } + + @Test + public void verifyResponse() { + final Response resp = this.googleAccountsService.getResponse("ticketId"); + assertEquals(resp.getResponseType(), DefaultResponse.ResponseType.POST); + assertTrue(resp.getAttributes().containsKey(SamlProtocolConstants.PARAMETER_SAML_RESPONSE)); + assertTrue(resp.getAttributes().containsKey(SamlProtocolConstants.PARAMETER_SAML_RELAY_STATE)); + } + + private static String encodeMessage(final String xmlString) throws IOException { + return CompressionUtils.deflate(xmlString); + } +} diff --git a/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/authentication/principal/SamlServiceTests.java b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/authentication/principal/SamlServiceTests.java new file mode 100644 index 000000000000..978563e39be0 --- /dev/null +++ b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/authentication/principal/SamlServiceTests.java @@ -0,0 +1,109 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.authentication.principal; + +import org.jasig.cas.authentication.principal.Response; +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.support.saml.AbstractOpenSamlTests; +import org.jasig.cas.support.saml.web.support.SamlArgumentExtractor; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +import static org.junit.Assert.*; + +/** + * Test cases for {@link SamlService}. + * @author Scott Battaglia + * @since 3.1 + */ +public class SamlServiceTests extends AbstractOpenSamlTests { + + @Test + public void verifyResponse() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("TARGET", "service"); + final SamlService impl = SamlService.createServiceFrom(request); + + final Response response = impl.getResponse("ticketId"); + assertNotNull(response); + assertEquals(Response.ResponseType.REDIRECT, response.getResponseType()); + assertTrue(response.getUrl().contains("SAMLart=")); + } + + @Test + public void verifyResponseForJsession() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("TARGET", "http://www.cnn.com/;jsession=test"); + final SamlService impl = SamlService.createServiceFrom(request); + + assertEquals("http://www.cnn.com/", impl.getId()); + } + + @Test + public void verifyResponseWithNoTicket() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("TARGET", "service"); + final SamlService impl = SamlService.createServiceFrom(request); + + final Response response = impl.getResponse(null); + assertNotNull(response); + assertEquals(Response.ResponseType.REDIRECT, response.getResponseType()); + assertFalse(response.getUrl().contains("SAMLart=")); + } + + @Test + public void verifyRequestBody() { + final String body = "" + + "" + + "artifact"; + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setContent(body.getBytes()); + + final SamlService impl = SamlService.createServiceFrom(request); + assertEquals("artifact", impl.getArtifactId()); + assertEquals("_192.168.16.51.1024506224022", impl.getRequestID()); + } + + @Test + public void verifyTargetMatchesingSamlService() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("TARGET", "https://some.service.edu/path/to/app"); + + final SamlArgumentExtractor ext = new SamlArgumentExtractor(); + final WebApplicationService service = ext.extractService(request); + + final SamlService impl = SamlService.createServiceFrom(request); + assertTrue(impl.matches(service)); + } + + @Test + public void verifyTargetMatchesNoSamlService() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("TARGET", "https://some.service.edu/path/to/app"); + final SamlService impl = SamlService.createServiceFrom(request); + + final MockHttpServletRequest request2 = new MockHttpServletRequest(); + request2.setParameter("TARGET", "https://some.SERVICE.edu"); + final SamlArgumentExtractor ext = new SamlArgumentExtractor(); + final WebApplicationService service = ext.extractService(request2); + + assertFalse(impl.matches(service)); + } +} diff --git a/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/util/SamlCompliantUniqueTicketIdGeneratorTests.java b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/util/SamlCompliantUniqueTicketIdGeneratorTests.java new file mode 100644 index 000000000000..e91451725fe7 --- /dev/null +++ b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/util/SamlCompliantUniqueTicketIdGeneratorTests.java @@ -0,0 +1,45 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.util; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * Test cases for {@link SamlCompliantUniqueTicketIdGenerator}. + * @author Scott Battaglia + * @since 3.4.3 + */ +public final class SamlCompliantUniqueTicketIdGeneratorTests { + + @Test + public void verifySaml1Compliant() { + final SamlCompliantUniqueTicketIdGenerator g = new SamlCompliantUniqueTicketIdGenerator("http://www.cnn.com"); + assertNotNull(g.getNewTicketId("TT")); + } + + @Test + public void verifySaml2Compliant() { + final SamlCompliantUniqueTicketIdGenerator g = new SamlCompliantUniqueTicketIdGenerator("http://www.cnn.com"); + g.setSaml2compliant(true); + assertNotNull(g.getNewTicketId("TT")); + + } +} diff --git a/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/flow/mdui/SamlMetadataUIParserActionTests.java b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/flow/mdui/SamlMetadataUIParserActionTests.java new file mode 100644 index 000000000000..61d2b55265c4 --- /dev/null +++ b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/flow/mdui/SamlMetadataUIParserActionTests.java @@ -0,0 +1,92 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.saml.web.flow.mdui; + +import org.jasig.cas.support.saml.AbstractOpenSamlTests; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.test.MockRequestContext; + +import static org.junit.Assert.*; + +/** + * This is {@link SamlMetadataUIParserActionTests}. + * + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class SamlMetadataUIParserActionTests extends AbstractOpenSamlTests { + + @Autowired + @Qualifier("samlMetadataUIParserAction") + private SamlMetadataUIParserAction samlMetadataUIParserAction; + + @Autowired + @Qualifier("samlDynamicMetadataUIParserAction") + private SamlMetadataUIParserAction samlDynamicMetadataUIParserAction; + + @Test + public void verifyEntityIdUIInfoExists() throws Exception { + final MockRequestContext ctx = new MockRequestContext(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter(SamlMetadataUIParserAction.ENTITY_ID_PARAMETER_NAME, "https://carmenwiki.osu.edu/shibboleth"); + + final MockHttpServletResponse response = new MockHttpServletResponse(); + + final MockServletContext sCtx = new MockServletContext(); + ctx.setExternalContext(new ServletExternalContext(sCtx, request, response)); + samlMetadataUIParserAction.doExecute(ctx); + assertTrue(ctx.getFlowScope().contains(SamlMetadataUIParserAction.MDUI_FLOW_PARAMETER_NAME)); + } + + @Test + public void verifyEntityIdUIInfoExistsDynamically() throws Exception { + final MockRequestContext ctx = new MockRequestContext(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter(SamlMetadataUIParserAction.ENTITY_ID_PARAMETER_NAME, "https://carmenwiki.osu.edu/shibboleth"); + + final MockHttpServletResponse response = new MockHttpServletResponse(); + + final MockServletContext sCtx = new MockServletContext(); + ctx.setExternalContext(new ServletExternalContext(sCtx, request, response)); + samlDynamicMetadataUIParserAction.doExecute(ctx); + assertTrue(ctx.getFlowScope().contains(SamlMetadataUIParserAction.MDUI_FLOW_PARAMETER_NAME)); + } + + @Test + public void verifyEntityIdUIInfoNoParam() throws Exception { + final MockRequestContext ctx = new MockRequestContext(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("somethingelse", "https://carmenwiki.osu.edu/shibboleth"); + + final MockHttpServletResponse response = new MockHttpServletResponse(); + + final MockServletContext sCtx = new MockServletContext(); + ctx.setExternalContext(new ServletExternalContext(sCtx, request, response)); + samlMetadataUIParserAction.doExecute(ctx); + assertFalse(ctx.getFlowScope().contains(SamlMetadataUIParserAction.MDUI_FLOW_PARAMETER_NAME)); + } + +} diff --git a/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/support/GoogleAccountsArgumentExtractorTests.java b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/support/GoogleAccountsArgumentExtractorTests.java new file mode 100644 index 000000000000..f127ee3a030c --- /dev/null +++ b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/support/GoogleAccountsArgumentExtractorTests.java @@ -0,0 +1,73 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.web.support; + +import java.security.PrivateKey; +import java.security.PublicKey; + +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.support.saml.AbstractOpenSamlTests; +import org.jasig.cas.util.PrivateKeyFactoryBean; +import org.jasig.cas.util.PublicKeyFactoryBean; +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; +import org.springframework.mock.web.MockHttpServletRequest; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +/** + * + * @author Scott Battaglia + * @since 3.1 + * + */ +public class GoogleAccountsArgumentExtractorTests extends AbstractOpenSamlTests { + + private GoogleAccountsArgumentExtractor extractor; + + @Before + public void setUp() throws Exception { + final PublicKeyFactoryBean pubKeyFactoryBean = new PublicKeyFactoryBean(); + final PrivateKeyFactoryBean privKeyFactoryBean = new PrivateKeyFactoryBean(); + + pubKeyFactoryBean.setAlgorithm("DSA"); + privKeyFactoryBean.setAlgorithm("DSA"); + + final ClassPathResource pubKeyResource = new ClassPathResource("DSAPublicKey01.key"); + final ClassPathResource privKeyResource = new ClassPathResource("DSAPrivateKey01.key"); + + pubKeyFactoryBean.setLocation(pubKeyResource); + privKeyFactoryBean.setLocation(privKeyResource); + assertTrue(privKeyFactoryBean.getObjectType().equals(PrivateKey.class)); + assertTrue(pubKeyFactoryBean.getObjectType().equals(PublicKey.class)); + pubKeyFactoryBean.afterPropertiesSet(); + privKeyFactoryBean.afterPropertiesSet(); + + final ServicesManager servicesManager = mock(ServicesManager.class); + + this.extractor = new GoogleAccountsArgumentExtractor((PublicKey) pubKeyFactoryBean.getObject(), + (PrivateKey) privKeyFactoryBean.getObject(), servicesManager); + } + + @Test + public void verifyNoService() { + assertNull(this.extractor.extractService(new MockHttpServletRequest())); + } +} diff --git a/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/support/SamlArgumentExtractorTests.java b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/support/SamlArgumentExtractorTests.java new file mode 100644 index 000000000000..ca4c9cf4fb65 --- /dev/null +++ b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/support/SamlArgumentExtractorTests.java @@ -0,0 +1,56 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.web.support; + +import static org.junit.Assert.*; + +import org.jasig.cas.authentication.principal.Service; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +/** + * Test cases for {@link SamlArgumentExtractor}. + * @author Scott Battaglia + * @since 3.1 + * + */ +public class SamlArgumentExtractorTests { + + private SamlArgumentExtractor extractor; + + @Before + public void setUp() throws Exception { + this.extractor = new SamlArgumentExtractor(); + } + + @Test + public void verifyObtainService() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("TARGET", "test"); + final Service service = this.extractor.extractService(request); + assertEquals("test", service.getId()); + } + + @Test + public void verifyServiceDoesNotExist() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + assertNull(this.extractor.extractService(request)); + } +} diff --git a/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/support/WebUtilTests.java b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/support/WebUtilTests.java new file mode 100644 index 000000000000..24f7b24472e6 --- /dev/null +++ b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/support/WebUtilTests.java @@ -0,0 +1,66 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.web.support; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.web.support.ArgumentExtractor; +import org.jasig.cas.web.support.CasArgumentExtractor; +import org.jasig.cas.web.support.WebUtils; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +/** + * @author Scott Battaglia + * @since 3.1 + */ +public class WebUtilTests { + + @Test + public void verifyFindService() { + final SamlArgumentExtractor openIdArgumentExtractor = new SamlArgumentExtractor(); + final CasArgumentExtractor casArgumentExtractor = new CasArgumentExtractor(); + final ArgumentExtractor[] argumentExtractors = new ArgumentExtractor[] { + openIdArgumentExtractor, casArgumentExtractor}; + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("service", "test"); + + final Service service = WebUtils.getService(Arrays + .asList(argumentExtractors), request); + + assertEquals("test", service.getId()); + } + + @Test + public void verifyFoundNoService() { + final SamlArgumentExtractor openIdArgumentExtractor = new SamlArgumentExtractor(); + final ArgumentExtractor[] argumentExtractors = new ArgumentExtractor[] { + openIdArgumentExtractor}; + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("service", "test"); + + final Service service = WebUtils.getService(Arrays + .asList(argumentExtractors), request); + + assertNull(service); + } +} diff --git a/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/view/Saml10FailureResponseViewTests.java b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/view/Saml10FailureResponseViewTests.java new file mode 100644 index 000000000000..c69745edb115 --- /dev/null +++ b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/view/Saml10FailureResponseViewTests.java @@ -0,0 +1,57 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.web.view; + +import static org.junit.Assert.*; + +import java.util.Collections; + +import org.jasig.cas.support.saml.AbstractOpenSamlTests; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +/** + * Unit test for {@link Saml10FailureResponseView} class + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.1 + * + */ +public class Saml10FailureResponseViewTests extends AbstractOpenSamlTests { + + private final Saml10FailureResponseView view = new Saml10FailureResponseView(); + + @Test + public void verifyResponse() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + request.addParameter("TARGET", "service"); + + final String description = "Validation failed"; + this.view.renderMergedOutputModel( + Collections.singletonMap("description", description), request, response); + + final String responseText = response.getContentAsString(); + assertTrue(responseText.contains("Status")); + assertTrue(responseText.contains(description)); + } + +} diff --git a/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/view/Saml10SuccessResponseViewTests.java b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/view/Saml10SuccessResponseViewTests.java new file mode 100644 index 000000000000..455e6e11d35e --- /dev/null +++ b/cas-server-support-saml/src/test/java/org/jasig/cas/support/saml/web/view/Saml10SuccessResponseViewTests.java @@ -0,0 +1,180 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.saml.web.view; + +import org.jasig.cas.CasProtocolConstants; +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.RememberMeCredential; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.services.DefaultServicesManagerImpl; +import org.jasig.cas.services.InMemoryServiceRegistryDaoImpl; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.RegisteredServiceImpl; +import org.jasig.cas.services.ReturnAllAttributeReleasePolicy; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.support.saml.AbstractOpenSamlTests; +import org.jasig.cas.support.saml.authentication.SamlAuthenticationMetaDataPopulator; +import org.jasig.cas.validation.Assertion; +import org.jasig.cas.validation.ImmutableAssertion; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; + +/** + * Unit test for {@link Saml10SuccessResponseView} class. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.1 + * + */ +public class Saml10SuccessResponseViewTests extends AbstractOpenSamlTests { + + private Saml10SuccessResponseView response; + + @Before + public void setUp() throws Exception { + + final List list = new ArrayList<>(); + + final RegisteredServiceImpl regSvc = new RegisteredServiceImpl(); + regSvc.setServiceId(TestUtils.getService().getId()); + regSvc.setName("Test Service"); + regSvc.setAttributeReleasePolicy(new ReturnAllAttributeReleasePolicy()); + + list.add(regSvc); + final InMemoryServiceRegistryDaoImpl dao = new InMemoryServiceRegistryDaoImpl(); + dao.setRegisteredServices(list); + final ServicesManager servicesManager = new DefaultServicesManagerImpl(dao); + this.response = new Saml10SuccessResponseView(); + this.response.setIssuer("testIssuer"); + this.response.setIssueLength(1000); + } + + @Test + public void verifyResponse() throws Exception { + final Map model = new HashMap<>(); + + final Map attributes = new HashMap<>(); + attributes.put("testAttribute", "testValue"); + attributes.put("testEmptyCollection", Collections.emptyList()); + attributes.put("testAttributeCollection", Arrays.asList("tac1", "tac2")); + final Principal principal = new DefaultPrincipalFactory().createPrincipal("testPrincipal", attributes); + + final Map authAttributes = new HashMap<>(); + authAttributes.put( + SamlAuthenticationMetaDataPopulator.ATTRIBUTE_AUTHENTICATION_METHOD, + SamlAuthenticationMetaDataPopulator.AUTHN_METHOD_SSL_TLS_CLIENT); + authAttributes.put("testSamlAttribute", "value"); + + final Authentication primary = TestUtils.getAuthentication(principal, authAttributes); + final Assertion assertion = new ImmutableAssertion( + primary, Collections.singletonList(primary), TestUtils.getService(), true); + model.put("assertion", assertion); + + final MockHttpServletResponse servletResponse = new MockHttpServletResponse(); + + this.response.renderMergedOutputModel(model, new MockHttpServletRequest(), servletResponse); + final String written = servletResponse.getContentAsString(); + + assertTrue(written.contains("testPrincipal")); + assertTrue(written.contains("testAttribute")); + assertTrue(written.contains("testValue")); + assertFalse(written.contains("testEmptyCollection")); + assertTrue(written.contains("testAttributeCollection")); + assertTrue(written.contains("tac1")); + assertTrue(written.contains("tac2")); + assertTrue(written.contains(SamlAuthenticationMetaDataPopulator.AUTHN_METHOD_SSL_TLS_CLIENT)); + assertTrue(written.contains("AuthenticationMethod")); + assertTrue(written.contains("AssertionID")); + } + + @Test + public void verifyResponseWithNoAttributes() throws Exception { + final Map model = new HashMap<>(); + + final Principal principal = new DefaultPrincipalFactory().createPrincipal("testPrincipal"); + + final Map authAttributes = new HashMap<>(); + authAttributes.put( + SamlAuthenticationMetaDataPopulator.ATTRIBUTE_AUTHENTICATION_METHOD, + SamlAuthenticationMetaDataPopulator.AUTHN_METHOD_SSL_TLS_CLIENT); + authAttributes.put("testSamlAttribute", "value"); + + final Authentication primary = TestUtils.getAuthentication(principal, authAttributes); + + final Assertion assertion = new ImmutableAssertion( + primary, Collections.singletonList(primary), TestUtils.getService(), true); + model.put("assertion", assertion); + + final MockHttpServletResponse servletResponse = new MockHttpServletResponse(); + + this.response.renderMergedOutputModel(model, new MockHttpServletRequest(), servletResponse); + final String written = servletResponse.getContentAsString(); + + assertTrue(written.contains("testPrincipal")); + assertTrue(written.contains(SamlAuthenticationMetaDataPopulator.AUTHN_METHOD_SSL_TLS_CLIENT)); + assertTrue(written.contains("AuthenticationMethod=")); + } + + @Test + public void verifyResponseWithoutAuthMethod() throws Exception { + final Map model = new HashMap<>(); + + final Map attributes = new HashMap<>(); + attributes.put("testAttribute", "testValue"); + final Principal principal = new DefaultPrincipalFactory().createPrincipal("testPrincipal", attributes); + + final Map authnAttributes = new HashMap<>(); + authnAttributes.put("authnAttribute1", "authnAttrbuteV1"); + authnAttributes.put("authnAttribute2", "authnAttrbuteV2"); + authnAttributes.put(RememberMeCredential.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME, Boolean.TRUE); + + final Authentication primary = TestUtils.getAuthentication(principal, authnAttributes); + + final Assertion assertion = new ImmutableAssertion( + primary, Collections.singletonList(primary), TestUtils.getService(), true); + model.put("assertion", assertion); + + final MockHttpServletResponse servletResponse = new MockHttpServletResponse(); + + this.response.renderMergedOutputModel(model, new MockHttpServletRequest(), servletResponse); + final String written = servletResponse.getContentAsString(); + + assertTrue(written.contains("testPrincipal")); + assertTrue(written.contains("testAttribute")); + assertTrue(written.contains("testValue")); + assertTrue(written.contains("authnAttribute1")); + assertTrue(written.contains("authnAttribute2")); + assertTrue(written.contains(CasProtocolConstants.VALIDATION_REMEMBER_ME_ATTRIBUTE_NAME)); + assertTrue(written.contains("urn:oasis:names:tc:SAML:1.0:am:unspecified")); + } +} diff --git a/cas-server-support-saml/src/test/resources/META-INF/spring/opensaml-config.xml b/cas-server-support-saml/src/test/resources/META-INF/spring/opensaml-config.xml new file mode 100644 index 000000000000..da9200d2ce64 --- /dev/null +++ b/cas-server-support-saml/src/test/resources/META-INF/spring/opensaml-config.xml @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-support-saml/src/test/resources/inc-md-cert.pem b/cas-server-support-saml/src/test/resources/inc-md-cert.pem new file mode 100644 index 000000000000..5ec4ec64e9df --- /dev/null +++ b/cas-server-support-saml/src/test/resources/inc-md-cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDgTCCAmmgAwIBAgIJAJRJzvdpkmNaMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV +BAYTAlVTMRUwEwYDVQQKDAxJbkNvbW1vbiBMTEMxMTAvBgNVBAMMKEluQ29tbW9u +IEZlZGVyYXRpb24gTWV0YWRhdGEgU2lnbmluZyBLZXkwHhcNMTMxMjE2MTkzNDU1 +WhcNMzcxMjE4MTkzNDU1WjBXMQswCQYDVQQGEwJVUzEVMBMGA1UECgwMSW5Db21t +b24gTExDMTEwLwYDVQQDDChJbkNvbW1vbiBGZWRlcmF0aW9uIE1ldGFkYXRhIFNp +Z25pbmcgS2V5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Chdkrn+ +dG5Zj5L3UIw+xeWgNzm8ajw7/FyqRQ1SjD4Lfg2WCdlfjOrYGNnVZMCTfItoXTSp +g4rXxHQsykeNiYRu2+02uMS+1pnBqWjzdPJE0od+q8EbdvE6ShimjyNn0yQfGyQK +CNdYuc+75MIHsaIOAEtDZUST9Sd4oeU1zRjV2sGvUd+JFHveUAhRc0b+JEZfIEuq +/LIU9qxm/+gFaawlmojZPyOWZ1JlswbrrJYYyn10qgnJvjh9gZWXKjmPxqvHKJcA +TPhAh2gWGabWTXBJCckMe1hrHCl/vbDLCmz0/oYuoaSDzP6zE9YSA/xCplaHA0mo +C1Vs2H5MOQGlewIDAQABo1AwTjAdBgNVHQ4EFgQU5ij9YLU5zQ6K75kPgVpyQ2N/ +lPswHwYDVR0jBBgwFoAU5ij9YLU5zQ6K75kPgVpyQ2N/lPswDAYDVR0TBAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAQEAaQkEx9xvaLUt0PNLvHMtxXQPedCPw5xQBd2V +WOsWPYspRAOSNbU1VloY+xUkUKorYTogKUY1q+uh2gDIEazW0uZZaQvWPp8xdxWq +Dh96n5US06lszEc+Lj3dqdxWkXRRqEbjhBFh/utXaeyeSOtaX65GwD5svDHnJBcl +AGkzeRIXqxmYG+I2zMm/JYGzEnbwToyC7yF6Q8cQxOr37hEpqz+WN/x3qM2qyBLE +CQFjmlJrvRLkSL15PCZiu+xFNFd/zx6btDun5DBlfDS9DG+SHCNH6Nq+NfP+ZQ8C +GzP/3TaZPzMlKPDCjp0XOQfyQqFIXdwjPFTWjEusDBlm4qJAlQ== +-----END CERTIFICATE----- diff --git a/cas-server-support-saml/src/test/resources/inc-md-pub.pem b/cas-server-support-saml/src/test/resources/inc-md-pub.pem new file mode 100644 index 000000000000..d35611cf28fa --- /dev/null +++ b/cas-server-support-saml/src/test/resources/inc-md-pub.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Chdkrn+dG5Zj5L3UIw+ +xeWgNzm8ajw7/FyqRQ1SjD4Lfg2WCdlfjOrYGNnVZMCTfItoXTSpg4rXxHQsykeN +iYRu2+02uMS+1pnBqWjzdPJE0od+q8EbdvE6ShimjyNn0yQfGyQKCNdYuc+75MIH +saIOAEtDZUST9Sd4oeU1zRjV2sGvUd+JFHveUAhRc0b+JEZfIEuq/LIU9qxm/+gF +aawlmojZPyOWZ1JlswbrrJYYyn10qgnJvjh9gZWXKjmPxqvHKJcATPhAh2gWGabW +TXBJCckMe1hrHCl/vbDLCmz0/oYuoaSDzP6zE9YSA/xCplaHA0moC1Vs2H5MOQGl +ewIDAQAB +-----END PUBLIC KEY----- diff --git a/cas-server-support-saml/src/test/resources/log4j2.xml b/cas-server-support-saml/src/test/resources/log4j2.xml new file mode 100644 index 000000000000..e4e739dce760 --- /dev/null +++ b/cas-server-support-saml/src/test/resources/log4j2.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-support-saml/src/test/resources/sample-metadata.xml b/cas-server-support-saml/src/test/resources/sample-metadata.xml new file mode 100644 index 000000000000..7ad493d08e37 --- /dev/null +++ b/cas-server-support-saml/src/test/resources/sample-metadata.xml @@ -0,0 +1,94 @@ + + + + + + + + http://id.incommon.org/category/research-and-scholarship + http://refeds.org/category/research-and-scholarship + http://id.incommon.org/category/registered-by-incommon + + + + + + + + + + + CarmenWiki + Enterprise Wiki Service at the Ohio State University. + https://ocio.osu.edu/services/view/carmenwiki-wiki-services + https://carmenwiki.osu.edu/x/jyLeAQ + https://carmenwiki.osu.edu/download/attachments/9666561/global.logo + + + + + + + +MIIDGzCCAgOgAwIBAgIJANI+yGM0M1N2MA0GCSqGSIb3DQEBBQUAMCcxJTAjBgNV +BAMTHGx0Y2F3aWtpMDEuaXQub2hpby1zdGF0ZS5lZHUwHhcNMTAwNzA3MjI0MzA1 +WhcNMjAwNzA0MjI0MzA1WjAnMSUwIwYDVQQDExxsdGNhd2lraTAxLml0Lm9oaW8t +c3RhdGUuZWR1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5fsEv25M +r9wfa48qfjn8m40yB/lwimJ8dSnYw2erd/tfB+sPESw42Is5Lv2B3pI3mj9a0PT0 +Gf1VgUoQW0RCT6L4VOW50WsPFv/RKPfT/AIRl00dTCqb440PgotGbrK9ivZqlvkz +lSGUKuFcg2gLj+CJlbMcwEneSwn0FE1xKEGpMDUk91lZH1XxmnIDDOQn1G5qul4q +AbXITMpLi2MlsHAEXxnLrthFFas6zDrviTwHcqGXq9zJJkPHDcbu1qg6AUT7bRJr +qszxxktSV6mFclkgLPpcVkigMR8RNVMQkWaaWSnfBkFy2iAe3xw3DNp7obtzgItY +i9N8U6K5qorSkQIDAQABo0owSDAnBgNVHREEIDAeghxsdGNhd2lraTAxLml0Lm9o +aW8tc3RhdGUuZWR1MB0GA1UdDgQWBBR32XnCliG78DdyTtZhyIQSHChtyjANBgkq +hkiG9w0BAQUFAAOCAQEAVEweCxPElHGmam4Iv2QeJsGE7m4de7axp3epAJb7uVbN +Z2P1S/s4GZQhmGsUoGoxwqca3wyQ+C1ZkpQJdyFl5s1tFc26D+Z0KTDo174GzO9i +I9SeQ4YSp3FNhZqxn4xH3DULzzHwoVSwFr5irLPAVtrqK8H/rzBREhqOse2VSJ/1 +PkI+p7lUiElIzMiObLGjumF2fDOPkXOSMNyC4c5oCCJtcrip/BaLo6bqdqn3DKP8 +onMw/lHZQolyVsupuhGsSX13WVJ0uyGvuA7hiHnGEkpDmskUd3TsriyQAt47RZzY +tTupO/NdWvz8SvXU1qIOk9CTQ0D2b2OOftfUW+FuAQ== + + + + + + + + + + + + + + + + CarmenWiki + Enterprise Wiki Service at the Ohio State University. + + + + + + + + + diff --git a/cas-server-3.4.2/cas-server-support-legacy/.cvsignore b/cas-server-support-spnego/.cvsignore similarity index 100% rename from cas-server-3.4.2/cas-server-support-legacy/.cvsignore rename to cas-server-support-spnego/.cvsignore diff --git a/cas-server-support-spnego/NOTICE b/cas-server-support-spnego/NOTICE new file mode 100644 index 000000000000..4d07c826236b --- /dev/null +++ b/cas-server-support-spnego/NOTICE @@ -0,0 +1,103 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS SPNEGO/NTLM Support under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + CDI APIs under Apache License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + jcifs under GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + org.samba.jcifs:jcifs-ext under GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-support-spnego/pom.xml b/cas-server-support-spnego/pom.xml new file mode 100644 index 000000000000..be5b20cf4966 --- /dev/null +++ b/cas-server-support-spnego/pom.xml @@ -0,0 +1,90 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-support-spnego + jar + Apereo CAS SPNEGO/NTLM Support + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + org.ldaptive + ldaptive + ${ldaptive.version} + + + + jcifs + jcifs + ${jcifs.version} + + + + org.samba.jcifs + jcifs-ext + ${jcifs.ext.version} + + + + org.springframework.webflow + spring-webflow + compile + + + + org.jasig.cas + cas-server-support-ldap + ${project.version} + test-jar + test + + + + com.unboundid + unboundid-ldapsdk + test + + + + org.ldaptive + ldaptive-unboundid + ${ldaptive.version} + test + + + + + ${project.parent.basedir} + 1.3.17 + 0.9.4 + + diff --git a/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/authentication/handler/support/JCIFSConfig.java b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/authentication/handler/support/JCIFSConfig.java new file mode 100644 index 000000000000..ce30fa2d14ca --- /dev/null +++ b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/authentication/handler/support/JCIFSConfig.java @@ -0,0 +1,225 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.spnego.authentication.handler.support; + +import jcifs.Config; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; + +import java.net.URL; + +/** + * Configuration helper for JCIFS and the Spring framework. + * + * @author Marc-Antoine Garrigue + * @author Arnaud Lesueur + * @author Scott Battaglia + * @deprecated As of 4.1, the class name is abbreviated in a way that is not per camel-casing standards and will be renamed in the future. + * @since 3.1 + */ +@Deprecated +public final class JCIFSConfig implements InitializingBean { + + private static final String DEFAULT_LOGIN_CONFIG = "/login.conf"; + + private static final String SYS_PROP_USE_SUBJECT_CRED_ONLY = "javax.security.auth.useSubjectCredsOnly"; + + private static final String SYS_PROP_LOGIN_CONF = "java.security.auth.login.config"; + + private static final String SYS_PROP_KERBEROS_DEBUG = "sun.security.krb5.debug"; + + private static final String SYS_PROP_KERBEROS_CONF = "java.security.krb5.conf"; + + private static final String SYS_PROP_KERBEROS_REALM = "java.security.krb5.realm"; + + private static final String SYS_PROP_KERBEROS_KDC = "java.security.krb5.kdc"; + + private static final String JCIFS_PROP_DOMAIN_CONTROLLER = "jcifs.http.domainController"; + + private static final String JCIFS_PROP_NETBIOS_WINS = "jcifs.netbios.wins"; + + private static final String JCIFS_PROP_CLIENT_DOMAIN = "jcifs.smb.client.domain"; + + private static final String JCIFS_PROP_CLIENT_USERNAME = "jcifs.smb.client.username"; + + private static final String JCIFS_PROP_CLIENT_PASSWORD = "jcifs.smb.client.password"; + + /** + * -- the service principal you just created. Using the previous example, + * this would be "HTTP/mybox at DOMAIN.COM". + */ + private static final String JCIFS_PROP_SERVICE_PRINCIPAL = "jcifs.spnego.servicePrincipal"; + + /** + * The password for the service principal account, required only if you + * decide not to use keytab. + */ + private static final String JCIFS_PROP_SERVICE_PASSWORD = "jcifs.spnego.servicePassword"; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private String loginConf; + + + /** + * Instantiates a new jCIFS config. + */ + public JCIFSConfig() { + Config.setProperty("jcifs.smb.client.soTimeout", "300000"); + Config.setProperty("jcifs.netbios.cachePolicy", "600"); + } + + @Override + public void afterPropertiesSet() throws Exception { + final String propValue = System.getProperty(SYS_PROP_LOGIN_CONF); + if (propValue != null) { + logger.warn("found login config in system property, may overide : {}", propValue); + } + + URL url = getClass().getResource( + this.loginConf == null ? DEFAULT_LOGIN_CONFIG : this.loginConf); + if (url != null) { + this.loginConf = url.toExternalForm(); + } + if (this.loginConf != null) { + System.setProperty(SYS_PROP_LOGIN_CONF, this.loginConf); + } else { + url = getClass().getResource("/jcifs/http/login.conf"); + if (url != null) { + System.setProperty(SYS_PROP_LOGIN_CONF, url.toExternalForm()); + } + } + logger.debug("configured login configuration path : {}", propValue); + } + + /** + * Sets the jcifs service password. + * + * @param jcifsServicePassword the new jcifs service password + */ + public void setJcifsServicePassword(final String jcifsServicePassword) { + logger.debug("jcifsServicePassword is set to *****"); + Config.setProperty(JCIFS_PROP_SERVICE_PASSWORD, jcifsServicePassword); + } + + /** + * Sets the jcifs service principal. + * + * @param jcifsServicePrincipal the new jcifs service principal + */ + public void setJcifsServicePrincipal(final String jcifsServicePrincipal) { + logger.debug("jcifsServicePrincipal is set to {}", jcifsServicePrincipal); + Config.setProperty(JCIFS_PROP_SERVICE_PRINCIPAL, jcifsServicePrincipal); + } + + /** + * Sets the kerberos conf. + * + * @param kerberosConf the new kerberos conf + */ + public void setKerberosConf(final String kerberosConf) { + logger.debug("kerberosConf is set to :{}", kerberosConf); + System.setProperty(SYS_PROP_KERBEROS_CONF, kerberosConf); + } + + /** + * Sets the kerberos kdc. + * + * @param kerberosKdc the new kerberos kdc + */ + public void setKerberosKdc(final String kerberosKdc) { + logger.debug("kerberosKdc is set to : {}", kerberosKdc); + System.setProperty(SYS_PROP_KERBEROS_KDC, kerberosKdc); + } + + /** + * Sets the kerberos realm. + * + * @param kerberosRealm the new kerberos realm + */ + public void setKerberosRealm(final String kerberosRealm) { + logger.debug("kerberosRealm is set to :{}", kerberosRealm); + System.setProperty(SYS_PROP_KERBEROS_REALM, kerberosRealm); + } + + public void setLoginConf(final String loginConf) { + this.loginConf = loginConf; + } + + /** + * Sets the use subject creds only. + * + * @param useSubjectCredsOnly the new use subject creds only + */ + public void setUseSubjectCredsOnly(final boolean useSubjectCredsOnly) { + logger.debug("useSubjectCredsOnly is set to {}", useSubjectCredsOnly); + System.setProperty(SYS_PROP_USE_SUBJECT_CRED_ONLY, Boolean.toString(useSubjectCredsOnly)); + } + + /** + * Sets the kerberos debug. + * + * @param kerberosDebug the new kerberos debug + */ + public void setKerberosDebug(final String kerberosDebug) { + logger.debug("kerberosDebug is set to : {}", kerberosDebug); + System.setProperty(SYS_PROP_KERBEROS_DEBUG, kerberosDebug); + } + + /** + * @param jcifsDomain the jcifsDomain to set + */ + public void setJcifsDomain(final String jcifsDomain) { + logger.debug("jcifsDomain is set to {}", jcifsDomain); + Config.setProperty(JCIFS_PROP_CLIENT_DOMAIN, jcifsDomain); + } + + /** + * @param jcifsDomainController the jcifsDomainController to set + */ + public void setJcifsDomainController(final String jcifsDomainController) { + logger.debug("jcifsDomainController is set to {}", jcifsDomainController); + Config.setProperty(JCIFS_PROP_DOMAIN_CONTROLLER, jcifsDomainController); + } + + /** + * @param jcifsPassword the jcifsPassword to set + */ + public void setJcifsPassword(final String jcifsPassword) { + Config.setProperty(JCIFS_PROP_CLIENT_PASSWORD, jcifsPassword); + logger.debug("jcifsPassword is set to *****"); + } + + /** + * @param jcifsUsername the jcifsUsername to set + */ + public void setJcifsUsername(final String jcifsUsername) { + logger.debug("jcifsUsername is set to {}", jcifsUsername); + Config.setProperty(JCIFS_PROP_CLIENT_USERNAME, jcifsUsername); + } + + /** + * @param jcifsNetbiosWins the jcifsNetbiosWins to set + */ + public void setJcifsNetbiosWins(final String jcifsNetbiosWins) { + logger.debug("jcifsNetbiosWins is set to {}", jcifsNetbiosWins); + Config.setProperty(JCIFS_PROP_NETBIOS_WINS, jcifsNetbiosWins); + } +} diff --git a/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/authentication/handler/support/JCIFSSpnegoAuthenticationHandler.java b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/authentication/handler/support/JCIFSSpnegoAuthenticationHandler.java new file mode 100644 index 000000000000..a92b4952ca72 --- /dev/null +++ b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/authentication/handler/support/JCIFSSpnegoAuthenticationHandler.java @@ -0,0 +1,166 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.spnego.authentication.handler.support; + +import jcifs.spnego.Authentication; +import org.jasig.cas.authentication.BasicCredentialMetaData; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.SimplePrincipal; +import org.jasig.cas.support.spnego.authentication.principal.SpnegoCredential; + +import javax.security.auth.login.FailedLoginException; +import java.security.GeneralSecurityException; +import java.util.regex.Pattern; + +/** + * Implementation of an AuthenticationHandler for SPNEGO supports. This Handler + * support both NTLM and Kerberos. NTLM is disabled by default. + * + * @author Arnaud Lesueur + * @author Marc-Antoine Garrigue + * @author Scott Battaglia + * @author Marvin S. Addison + * @deprecated As of 4.1, the class name is abbreviated in a way that is not per camel-casing standards and will be renamed in the future. + * @since 3.1 + */ +@Deprecated +public final class JCIFSSpnegoAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler { + + private Authentication authentication; + + /** + * Principal contains the DomainName ? (true by default). + */ + private boolean principalWithDomainName = true; + + /** + * Allow SPNEGO/NTLM Token as valid credentials. (false by default) + */ + private boolean isNTLMallowed; + + @Override + protected HandlerResult doAuthentication(final Credential credential) throws GeneralSecurityException, PreventedException { + final SpnegoCredential spnegoCredential = (SpnegoCredential) credential; + java.security.Principal principal; + byte[] nextToken; + try { + // proceed authentication using jcifs + synchronized (this) { + this.authentication.reset(); + this.authentication.process(spnegoCredential.getInitToken()); + principal = this.authentication.getPrincipal(); + nextToken = this.authentication.getNextToken(); + } + } catch (final jcifs.spnego.AuthenticationException e) { + throw new FailedLoginException(e.getMessage()); + } + + // evaluate jcifs response + if (nextToken != null) { + logger.debug("Setting nextToken in credential"); + spnegoCredential.setNextToken(nextToken); + } else { + logger.debug("nextToken is null"); + } + + boolean success = false; + if (principal != null) { + if (spnegoCredential.isNtlm()) { + logger.debug("NTLM Credential is valid for user [{}]", principal.getName()); + spnegoCredential.setPrincipal(getPrincipal(principal.getName(), true)); + success = this.isNTLMallowed; + } + // else => kerberos + logger.debug("Kerberos Credential is valid for user [{}]", principal.getName()); + spnegoCredential.setPrincipal(getPrincipal(principal.getName(), false)); + success = true; + } + + if (!success) { + throw new FailedLoginException("Principal is null, the processing of the SPNEGO Token failed"); + } + return new DefaultHandlerResult(this, new BasicCredentialMetaData(credential), spnegoCredential.getPrincipal()); + } + + @Override + public boolean supports(final Credential credential) { + return credential instanceof SpnegoCredential; + } + + public void setAuthentication(final Authentication authentication) { + this.authentication = authentication; + } + + public void setPrincipalWithDomainName(final boolean principalWithDomainName) { + this.principalWithDomainName = principalWithDomainName; + } + + public void setNTLMallowed(final boolean isNTLMallowed) { + this.isNTLMallowed = isNTLMallowed; + } + + /** + * @deprecated As of 4.1. Use {@link #getPrincipal(String, boolean)} + * Gets the simple principal from the given name. + * + * @param name the name + * @param isNtlm the is ntlm + * @return the simple principal + */ + @Deprecated + protected SimplePrincipal getSimplePrincipal(final String name, final boolean isNtlm) { + logger.warn("getSimplePrincipal() is deprecated and will be removed. Consider getPrincipal() instead."); + + if (this.principalWithDomainName) { + return (SimplePrincipal) new DefaultPrincipalFactory().createPrincipal(name); + } + if (isNtlm) { + return Pattern.matches("\\S+\\\\\\S+", name) + ? (SimplePrincipal) new DefaultPrincipalFactory().createPrincipal(name.split("\\\\")[1]) + : (SimplePrincipal) new DefaultPrincipalFactory().createPrincipal(name); + } + return (SimplePrincipal) new DefaultPrincipalFactory().createPrincipal(name.split("@")[0]); + } + + /** + * Gets the principal from the given name. The principal + * is created by the factory instance. + * + * @param name the name + * @param isNtlm the is ntlm + * @return the simple principal + */ + protected Principal getPrincipal(final String name, final boolean isNtlm) { + if (this.principalWithDomainName) { + return this.principalFactory.createPrincipal(name); + } + if (isNtlm) { + return Pattern.matches("\\S+\\\\\\S+", name) + ? this.principalFactory.createPrincipal(name.split("\\\\")[1]) + : this.principalFactory.createPrincipal(name); + } + return this.principalFactory.createPrincipal(name.split("@")[0]); + } +} diff --git a/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/authentication/handler/support/NtlmAuthenticationHandler.java b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/authentication/handler/support/NtlmAuthenticationHandler.java new file mode 100644 index 000000000000..1713ccb75225 --- /dev/null +++ b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/authentication/handler/support/NtlmAuthenticationHandler.java @@ -0,0 +1,149 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.spnego.authentication.handler.support; + +import jcifs.Config; +import jcifs.UniAddress; +import jcifs.netbios.NbtAddress; +import jcifs.ntlmssp.Type1Message; +import jcifs.ntlmssp.Type2Message; +import jcifs.ntlmssp.Type3Message; +import jcifs.smb.NtlmPasswordAuthentication; +import jcifs.smb.SmbAuthException; +import jcifs.smb.SmbSession; +import org.jasig.cas.authentication.BasicCredentialMetaData; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler; +import org.jasig.cas.support.spnego.authentication.principal.SpnegoCredential; + +import javax.security.auth.login.FailedLoginException; +import javax.validation.constraints.NotNull; +import java.security.GeneralSecurityException; + +/** + * Implementation of an AuthenticationHandler for NTLM supports. + * + * @author Julien Henry + * @author Scott Battaglia + * @author Arnaud Lesueur + * @since 3.1 + */ + +public class NtlmAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler { + + private static final int NBT_ADDRESS_TYPE = 0x1C; + private static final int NTLM_TOKEN_TYPE_FIELD_INDEX = 8; + private static final int NTLM_TOKEN_TYPE_ONE = 1; + private static final int NTLM_TOKEN_TYPE_THREE = 3; + private boolean loadBalance = true; + + @NotNull + private String domainController = Config.getProperty("jcifs.smb.client.domain"); + + private String includePattern; + + @Override + protected final HandlerResult doAuthentication( + final Credential credential) throws GeneralSecurityException, PreventedException { + + final SpnegoCredential ntlmCredential = (SpnegoCredential) credential; + final byte[] src = ntlmCredential.getInitToken(); + + UniAddress dc = null; + + boolean success = false; + try { + if (this.loadBalance) { + // find the first dc that matches the includepattern + if (this.includePattern != null) { + final NbtAddress[] dcs= NbtAddress.getAllByName(this.domainController, NBT_ADDRESS_TYPE, null, null); + for (final NbtAddress dc2 : dcs) { + if(dc2.getHostAddress().matches(this.includePattern)){ + dc = new UniAddress(dc2); + break; + } + } + } else { + dc = new UniAddress(NbtAddress.getByName(this.domainController, NBT_ADDRESS_TYPE, null)); + } + } else { + dc = UniAddress.getByName(this.domainController, true); + } + final byte[] challenge = SmbSession.getChallenge(dc); + + switch (src[NTLM_TOKEN_TYPE_FIELD_INDEX]) { + case NTLM_TOKEN_TYPE_ONE: + logger.debug("Type 1 received"); + final Type1Message type1 = new Type1Message(src); + final Type2Message type2 = new Type2Message(type1, + challenge, null); + logger.debug("Type 2 returned. Setting next token."); + ntlmCredential.setNextToken(type2.toByteArray()); + break; + case NTLM_TOKEN_TYPE_THREE: + logger.debug("Type 3 received"); + final Type3Message type3 = new Type3Message(src); + final byte[] lmResponse = type3.getLMResponse() == null ? new byte[0] : type3.getLMResponse(); + final byte[] ntResponse = type3.getNTResponse() == null ? new byte[0] : type3.getNTResponse(); + final NtlmPasswordAuthentication ntlm = new NtlmPasswordAuthentication( + type3.getDomain(), type3.getUser(), challenge, + lmResponse, ntResponse); + logger.debug("Trying to authenticate {} with domain controller", type3.getUser()); + try { + SmbSession.logon(dc, ntlm); + ntlmCredential.setPrincipal(this.principalFactory.createPrincipal(type3.getUser())); + success = true; + } catch (final SmbAuthException sae) { + throw new FailedLoginException(sae.getMessage()); + } + break; + default: + logger.debug("Unknown type: {}", src[NTLM_TOKEN_TYPE_FIELD_INDEX]); + } + } catch (final Exception e) { + throw new FailedLoginException(e.getMessage()); + } + + if (!success) { + throw new FailedLoginException(); + } + return new DefaultHandlerResult(this, new BasicCredentialMetaData(ntlmCredential), ntlmCredential.getPrincipal()); + } + + @Override + public boolean supports(final Credential credential) { + return credential instanceof SpnegoCredential; + } + + public void setLoadBalance(final boolean loadBalance) { + this.loadBalance = loadBalance; + } + + public void setDomainController(final String domainController) { + this.domainController = domainController; + } + + public void setIncludePattern(final String includePattern) { + this.includePattern = includePattern; + } + +} diff --git a/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/authentication/principal/SpnegoCredential.java b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/authentication/principal/SpnegoCredential.java new file mode 100644 index 000000000000..04db77b152bd --- /dev/null +++ b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/authentication/principal/SpnegoCredential.java @@ -0,0 +1,190 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.spnego.authentication.principal; + +import com.google.common.io.ByteSource; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.principal.Principal; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.Assert; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Arrays; + +/** + * Credential that are a holder for SPNEGO init token. + * + * @author Arnaud Lesueur + * @author Marc-Antoine Garrigue + * @since 3.1 + */ +public final class SpnegoCredential implements Credential, Serializable { + + /** + * Unique id for serialization. + */ + private static final long serialVersionUID = 84084596791289548L; + + private static final int NTLM_TOKEN_MAX_LENGTH = 8; + + private static final Byte CHAR_S_BYTE = Byte.valueOf((byte) 'S'); + + /** The ntlmssp signature. */ + private static final Byte[] NTLMSSP_SIGNATURE = {Byte.valueOf((byte) 'N'), + Byte.valueOf((byte) 'T'), Byte.valueOf((byte) 'L'), + Byte.valueOf((byte) 'M'), CHAR_S_BYTE, CHAR_S_BYTE, + Byte.valueOf((byte) 'P'), Byte.valueOf((byte) 0)}; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * The SPNEGO Init Token. + */ + private final ByteSource initToken; + + /** + * The SPNEGO Next Token. + */ + private ByteSource nextToken; + + /** + * The Principal. + */ + private Principal principal; + + /** + * The authentication type should be Kerberos or NTLM. + */ + private final boolean isNtlm; + + /** + * Instantiates a new SPNEGO credential. + * + * @param initToken the init token + */ + public SpnegoCredential(final byte[] initToken) { + Assert.notNull(initToken, "The initToken cannot be null."); + this.initToken = ByteSource.wrap(initToken); + this.isNtlm = isTokenNtlm(this.initToken); + } + + public byte[] getInitToken() { + return consumeByteSourceOrNull(this.initToken); + } + + public byte[] getNextToken() { + return consumeByteSourceOrNull(this.nextToken); + } + + /** + * Sets next token. + * + * @param nextToken the next token + */ + public void setNextToken(final byte[] nextToken) { + this.nextToken = ByteSource.wrap(nextToken); + } + + public Principal getPrincipal() { + return this.principal; + } + + public void setPrincipal(final Principal principal) { + this.principal = principal; + } + + public boolean isNtlm() { + return this.isNtlm; + } + + @Override + public String getId() { + return this.principal != null ? this.principal.getId() : UNKNOWN_ID; + } + + @Override + public String toString() { + return getId(); + } + + /** + * Checks if is token ntlm. + * + * @param tokenSource the token + * @return true, if token ntlm + */ + private boolean isTokenNtlm(final ByteSource tokenSource) { + + + final byte[] token = consumeByteSourceOrNull(tokenSource); + if (token == null || token.length < NTLM_TOKEN_MAX_LENGTH) { + return false; + } + for (int i = 0; i < NTLM_TOKEN_MAX_LENGTH; i++) { + if (NTLMSSP_SIGNATURE[i].byteValue() != token[i]) { + return false; + } + } + return true; + } + + @Override + public boolean equals(final Object obj) { + if (obj == null || !obj.getClass().equals(this.getClass())) { + return false; + } + + final SpnegoCredential c = (SpnegoCredential) obj; + + return Arrays.equals(this.getInitToken(), c.getInitToken()) + && this.principal.equals(c.getPrincipal()) + && Arrays.equals(this.getNextToken(), c.getNextToken()); + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + if (this.principal != null) { + hash = this.principal.hashCode(); + } + return new HashCodeBuilder().append(this.getInitToken()) + .append(this.getNextToken()) + .append(hash).toHashCode(); + } + + /** + * Read the contents of the source into a byte array. + * @param source the byte array source + * @return the byte[] read from the source or null + */ + private byte[] consumeByteSourceOrNull(final ByteSource source) { + try { + if (source == null || source.isEmpty()) { + return null; + } + return source.read(); + } catch (final IOException e) { + logger.warn("Could not consume the byte array source", e); + return null; + } + } +} diff --git a/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/authentication/principal/SpnegoPrincipalResolver.java b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/authentication/principal/SpnegoPrincipalResolver.java new file mode 100644 index 000000000000..efa359cfdfb3 --- /dev/null +++ b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/authentication/principal/SpnegoPrincipalResolver.java @@ -0,0 +1,67 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.spnego.authentication.principal; + +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver; + +import javax.validation.constraints.NotNull; +import java.util.Locale; + +/** + * Implementation of a CredentialToPrincipalResolver that takes a + * SpnegoCredential and returns a SimplePrincipal. + * + * @author Arnaud Lesueur + * @author Marc-Antoine Garrigue + * @since 3.1 + */ +public final class SpnegoPrincipalResolver extends PersonDirectoryPrincipalResolver { + + /** Tranformation types. **/ + public static enum Transform {NONE, UPPERCASE, LOWERCASE} + + @NotNull + private Transform transformPrincipalId = Transform.NONE; + + @Override + protected String extractPrincipalId(final Credential credential) { + final SpnegoCredential c = (SpnegoCredential) credential; + final String id = c.getPrincipal().getId(); + + switch (this.transformPrincipalId) { + case UPPERCASE: + return id.toUpperCase(Locale.ENGLISH); + case LOWERCASE: + return id.toLowerCase(Locale.ENGLISH); + default: + return id; + } + } + + @Override + public boolean supports(final Credential credential) { + return credential != null + && SpnegoCredential.class.equals(credential.getClass()); + } + + public void setTransformPrincipalId(final Transform transform) { + this.transformPrincipalId = transform; + } +} diff --git a/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/util/ReverseDNSRunnable.java b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/util/ReverseDNSRunnable.java new file mode 100644 index 000000000000..2688e49dc246 --- /dev/null +++ b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/util/ReverseDNSRunnable.java @@ -0,0 +1,97 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.spnego.util; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * Utility class to perform DNS work in a threaded, timeout-able way + * Adapted from: http://thushw.blogspot.com/2009/11/resolving-domain-names-quickly-with.html. + * + * @author Sean Baker sean.baker@usuhs.edu + * @author Misagh Moayyed + * @since 4.1 + */ +public final class ReverseDNSRunnable implements Runnable { + + /** Logger instance. **/ + private static final Logger LOGGER = LoggerFactory.getLogger(ReverseDNSRunnable.class); + + /** Remote user IP address. **/ + private final String ipAddress; + + /** Remote user hostname. **/ + private String hostName; + + /** + * Simple constructor which also pre-sets hostName attribute for failover situations. + * @param ipAddress the ip address on which reverse DNS will be done. + */ + public ReverseDNSRunnable(final String ipAddress) { + this.ipAddress = ipAddress; + this.hostName = ipAddress; + } + + /** + * Runnable implementation to thread the work done in this class, allowing the + * implementer to set a thread timeout and thereby short-circuit the lookup. + */ + @Override + public void run() { + try { + LOGGER.debug("Attempting to resolve {}", this.ipAddress); + final InetAddress address = InetAddress.getByName(this.ipAddress); + set(address.getCanonicalHostName()); + } catch (final UnknownHostException e) { + /** N/A -- Default to IP address, but that's already done. **/ + LOGGER.debug("Unable to identify the canonical hostname for ip address.", e); + } + } + + /** + * Glorified setter with logging. + * @param hostName the resolved hostname + */ + public synchronized void set(final String hostName) { + LOGGER.trace("ReverseDNS -- Found hostName: {}.", hostName); + this.hostName = hostName; + } + + /** + * Getter method to provide result of lookup. + * @return the remote host name, or the IP address if name not found + */ + public synchronized String get() { + return this.hostName; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("ipAddress", this.ipAddress) + .append("hostName", this.hostName) + .toString(); + } +} diff --git a/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/util/SpnegoConstants.java b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/util/SpnegoConstants.java new file mode 100644 index 000000000000..f811924c9df0 --- /dev/null +++ b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/util/SpnegoConstants.java @@ -0,0 +1,50 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.spnego.util; + +/** + * Spnego Constants. + * + * @author Arnaud Lesueur + * @author Marc-Antoine Garrigue + * @since 3.1 + */ +public interface SpnegoConstants { + + /** The header authenticate. */ + String HEADER_AUTHENTICATE = "WWW-Authenticate"; + + /** The header authorization. */ + String HEADER_AUTHORIZATION = "Authorization"; + + /** The header user agent. */ + String HEADER_USER_AGENT = "User-Agent"; + + /** The negotiate. */ + String NEGOTIATE = "Negotiate"; + + /** The spnego first time. */ + String SPNEGO_FIRST_TIME = "spnegoFirstTime"; + + /** The spnego credentials. */ + String SPNEGO_CREDENTIALS = "spnegoCredentials"; + + /** The ntlm. */ + String NTLM = "NTLM"; +} diff --git a/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/web/flow/SpnegoCredentialsAction.java b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/web/flow/SpnegoCredentialsAction.java new file mode 100644 index 000000000000..0a7666333060 --- /dev/null +++ b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/web/flow/SpnegoCredentialsAction.java @@ -0,0 +1,156 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.spnego.web.flow; + +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.support.spnego.authentication.principal.SpnegoCredential; +import org.jasig.cas.support.spnego.util.SpnegoConstants; +import org.jasig.cas.util.CompressionUtils; +import org.jasig.cas.web.flow.AbstractNonInteractiveCredentialsAction; +import org.jasig.cas.web.support.WebUtils; +import org.springframework.util.StringUtils; +import org.springframework.webflow.execution.RequestContext; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.nio.charset.Charset; + +/** + * Second action of a SPNEGO flow : decode the gssapi-data and build a new + * {@link org.jasig.cas.support.spnego.authentication.principal.SpnegoCredential}. + *

+ * Once AbstractNonInteractiveCredentialsAction has executed the authentication + * procedure, this action check whether a principal is present in Credential and + * add corresponding response headers.

+ * + * @author Arnaud Lesueur + * @author Marc-Antoine Garrigue + * @see RFC 4559 + * @since 3.1 + */ +public final class SpnegoCredentialsAction extends AbstractNonInteractiveCredentialsAction { + + + private boolean ntlm; + + private String messageBeginPrefix = constructMessagePrefix(); + + /** + * Behavior in case of SPNEGO authentication failure : + *
  • True : if SPNEGO is the last authentication method with no fallback.
  • + *
  • False : if an interactive view (eg: login page) should be send to user as SPNEGO failure fallback
  • + *
+ */ + private boolean send401OnAuthenticationFailure = true; + + @Override + protected Credential constructCredentialsFromRequest( + final RequestContext context) { + final HttpServletRequest request = WebUtils.getHttpServletRequest(context); + + final String authorizationHeader = request + .getHeader(SpnegoConstants.HEADER_AUTHORIZATION); + + if (StringUtils.hasText(authorizationHeader) + && authorizationHeader.startsWith(this.messageBeginPrefix) + && authorizationHeader.length() > this.messageBeginPrefix.length()) { + + logger.debug("SPNEGO Authorization header found with {} bytes", + authorizationHeader.length() - this.messageBeginPrefix.length()); + + final byte[] token = CompressionUtils.decodeBase64ToByteArray(authorizationHeader.substring(this.messageBeginPrefix.length())); + if (token == null) { + logger.warn("Could not compress authorization header in base64"); + return null; + } + logger.debug("Obtained token: {}", new String(token, Charset.defaultCharset())); + return new SpnegoCredential(token); + } + + return null; + } + + /** + * Construct message prefix. + * + * @return the string + */ + protected String constructMessagePrefix() { + return (this.ntlm ? SpnegoConstants.NTLM : SpnegoConstants.NEGOTIATE) + + ' '; + } + + @Override + protected void onError(final RequestContext context, + final Credential credential) { + setResponseHeader(context, credential); + } + + @Override + protected void onSuccess(final RequestContext context, + final Credential credential) { + setResponseHeader(context, credential); + } + + /** + * Sets the response header based on the retrieved tocken. + * + * @param context the context + * @param credential the credential + */ + private void setResponseHeader(final RequestContext context, + final Credential credential) { + if (credential == null) { + return; + } + + final HttpServletResponse response = WebUtils + .getHttpServletResponse(context); + final SpnegoCredential spnegoCredentials = (SpnegoCredential) credential; + final byte[] nextToken = spnegoCredentials.getNextToken(); + if (nextToken != null) { + logger.debug("Obtained output token: {}", new String(nextToken, Charset.defaultCharset())); + response.setHeader(SpnegoConstants.HEADER_AUTHENTICATE, (this.ntlm + ? SpnegoConstants.NTLM : SpnegoConstants.NEGOTIATE) + + ' ' + CompressionUtils.encodeBase64(nextToken)); + } else { + logger.debug("Unable to obtain the output token required."); + } + + if (spnegoCredentials.getPrincipal() == null && send401OnAuthenticationFailure) { + logger.debug("Setting HTTP Status to 401"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + } + + /** + * Sets the ntlm and generate message prefix. + * + * @param ntlm the new ntlm + */ + public void setNtlm(final boolean ntlm) { + this.ntlm = ntlm; + this.messageBeginPrefix = constructMessagePrefix(); + } + + public void setSend401OnAuthenticationFailure(final boolean send401OnAuthenticationFailure) { + this.send401OnAuthenticationFailure = send401OnAuthenticationFailure; + } + +} diff --git a/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/web/flow/SpnegoNegociateCredentialsAction.java b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/web/flow/SpnegoNegociateCredentialsAction.java new file mode 100644 index 000000000000..4f005d6bd8ae --- /dev/null +++ b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/web/flow/SpnegoNegociateCredentialsAction.java @@ -0,0 +1,199 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.spnego.web.flow; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.jasig.cas.support.spnego.util.SpnegoConstants; +import org.jasig.cas.web.support.WebUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; +import org.springframework.webflow.action.AbstractAction; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +/** + * First action of a SPNEGO flow : negotiation. + *

The server checks if the + * negotiation string is in the request header and this is a supported browser: + *

    + *
  • If found do nothing and return success()
  • + *
  • else add a WWW-Authenticate response header and a 401 response status, + * then return success()
  • + *
+ * + * @author Arnaud Lesueur + * @author Marc-Antoine Garrigue + * @author Scott Battaglia + * @author John Gasper + * @see RFC 4559 + * @since 3.1 + */ +public final class SpnegoNegociateCredentialsAction extends AbstractAction { + + private static final Logger LOGGER = LoggerFactory.getLogger(SpnegoNegociateCredentialsAction.class); + + /** Whether this is using the NTLM protocol or not. */ + private boolean ntlm; + + private boolean mixedModeAuthentication; + + private List supportedBrowser; + + private String messageBeginPrefix = constructMessagePrefix(); + + /** + * Instantiates a new Spnego negociate credentials action. + * Also initializes the list of supported browser user agents with the following: + *
    + *
  • MSIE
  • + *
  • Trident
  • + *
  • Firefox
  • + *
  • AppleWebKit
  • + *
+ * @see #setSupportedBrowser(List) + * @since 4.1 + */ + public SpnegoNegociateCredentialsAction() { + super(); + + this.supportedBrowser = new ArrayList<>(); + this.supportedBrowser.add("MSIE"); + this.supportedBrowser.add("Trident"); + this.supportedBrowser.add("Firefox"); + this.supportedBrowser.add("AppleWebKit"); + + } + + @Override + protected Event doExecute(final RequestContext context) { + final HttpServletRequest request = WebUtils.getHttpServletRequest(context); + final HttpServletResponse response = WebUtils.getHttpServletResponse(context); + + final String authorizationHeader = request.getHeader(SpnegoConstants.HEADER_AUTHORIZATION); + final String userAgent = request.getHeader(SpnegoConstants.HEADER_USER_AGENT); + + LOGGER.debug("Authorization header [{}], User Agent header [{}]", authorizationHeader, userAgent); + + if (!StringUtils.hasText(userAgent) || this.supportedBrowser.isEmpty()) { + LOGGER.debug("User Agent header [{}] is empty, or no browsers are supported", userAgent); + return success(); + } + + if (!isSupportedBrowser(userAgent)) { + LOGGER.debug("User Agent header [{}] is not supported in the list of supported browsers [{}]", + userAgent, this.supportedBrowser); + return success(); + } + + if (!StringUtils.hasText(authorizationHeader) + || !authorizationHeader.startsWith(this.messageBeginPrefix) + || authorizationHeader.length() <= this.messageBeginPrefix + .length()) { + + final String wwwHeader = this.ntlm ? SpnegoConstants.NTLM : SpnegoConstants.NEGOTIATE; + LOGGER.debug("Authorization header not found or does not match the message prefix [{}]. Sending [{}] header [{}]", + this.messageBeginPrefix, SpnegoConstants.HEADER_AUTHENTICATE, wwwHeader); + response.setHeader(SpnegoConstants.HEADER_AUTHENTICATE, wwwHeader); + + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + // The responseComplete flag tells the pausing view-state not to render the response + // because another object has taken care of it. If mixed mode authentication is allowed + // then responseComplete should not be called so that webflow will display the login page. + if (!this.mixedModeAuthentication) { + LOGGER.debug("Mixed-mode authentication is disabled. Executing completion of response"); + context.getExternalContext().recordResponseComplete(); + } + } + return success(); + } + + /** + * Sets the ntlm. Generates the message prefix as well. + * + * @param ntlm the new ntlm + */ + public void setNtlm(final boolean ntlm) { + this.ntlm = ntlm; + this.messageBeginPrefix = constructMessagePrefix(); + } + + /** + * Sets supported browsers by their user agent. The user agent + * header defined by {@link SpnegoConstants#HEADER_USER_AGENT} + * will be compared against this list. The user agents configured + * here need not be an exact match. So longer is the user agent identifier + * configured in this list is "found" in the user agent header retrieved, + * the check will pass. + * + * @param supportedBrowser the supported browsers list + */ + public void setSupportedBrowser(final List supportedBrowser) { + this.supportedBrowser = supportedBrowser; + } + + /** + * Sets whether mixed mode authentication should be enabled. If it is + * enabled then control is allowed to pass back to the Spring Webflow + * instead of immediately terminating the page after issuing the + * unauthorized (401) header. This has the effect of displaying the login + * page on unsupported/configured browsers. + *

+ * If this is set to false then the page is immediately closed after the + * unauthorized header is sent. This is ideal in environments that only + * want to use Windows Integrated Auth/SPNEGO and not forms auth. + * + * @param enabled should mixed mode authentication be allowed. Default is false. + */ + public void setMixedModeAuthentication(final boolean enabled) { + this.mixedModeAuthentication = enabled; + } + + /** + * Construct message prefix. + * + * @return if {@link #ntlm} is enabled, {@link SpnegoConstants#NTLM}, otherwise + * {@link SpnegoConstants#NEGOTIATE}. An extra space is appended to the end. + */ + protected String constructMessagePrefix() { + return (this.ntlm ? SpnegoConstants.NTLM : SpnegoConstants.NEGOTIATE) + + ' '; + } + + /** + * Checks if is supported browser. + * + * @param userAgent the user agent + * @return true, if supported browser + */ + protected boolean isSupportedBrowser(final String userAgent) { + for (final String supportedBrowser : this.supportedBrowser) { + if (userAgent.contains(supportedBrowser)) { + return true; + } + } + return false; + } +} diff --git a/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/web/flow/client/BaseSpnegoKnownClientSystemsFilterAction.java b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/web/flow/client/BaseSpnegoKnownClientSystemsFilterAction.java new file mode 100644 index 000000000000..a30848d0d187 --- /dev/null +++ b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/web/flow/client/BaseSpnegoKnownClientSystemsFilterAction.java @@ -0,0 +1,241 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.spnego.web.flow.client; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.jasig.cas.support.spnego.util.ReverseDNSRunnable; +import org.jasig.cas.web.support.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.webflow.action.AbstractAction; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.constraints.NotNull; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Abstract class for defining a simple binary filter to determine whether a + * given client system should be prompted for SPNEGO / KRB / NTLM credentials. + * + * Envisioned implementations would include LDAP and DNS based determinations, + * but of course others may have value as well for local architectures. + * + * @author Sean Baker sean.baker@usuhs.edu + * @author Misagh Moayyed + * @since 4.1 + */ +public class BaseSpnegoKnownClientSystemsFilterAction extends AbstractAction { + private static final int DEFAULT_TIMEOUT = 2000; + + /** Logger instance. **/ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** Pattern of ip addresses to check. **/ + private Pattern ipsToCheckPattern; + + /** Alternative remote host attribute. **/ + private String alternativeRemoteHostAttribute; + + /** Timeout for DNS Requests. **/ + private long timeout = DEFAULT_TIMEOUT; + + /** + * Instantiates a new Base. + */ + public BaseSpnegoKnownClientSystemsFilterAction() {} + + /** + * Instantiates a new Base. + * + * @param ipsToCheckPattern the ips to check pattern + */ + public BaseSpnegoKnownClientSystemsFilterAction(final String ipsToCheckPattern) { + setIpsToCheckPattern(ipsToCheckPattern); + } + + /** + * Instantiates a new Base. + * + * @param ipsToCheckPattern the ips to check pattern + * @param alternativeRemoteHostAttribute the alternative remote host attribute + */ + public BaseSpnegoKnownClientSystemsFilterAction(final String ipsToCheckPattern, + final String alternativeRemoteHostAttribute) { + setIpsToCheckPattern(ipsToCheckPattern); + this.alternativeRemoteHostAttribute = alternativeRemoteHostAttribute; + } + + /** + * Instantiates a new Base. + * + * @param ipsToCheckPattern the ips to check pattern + * @param alternativeRemoteHostAttribute the alternative remote host attribute + */ + public BaseSpnegoKnownClientSystemsFilterAction(final Pattern ipsToCheckPattern, + final String alternativeRemoteHostAttribute) { + this.ipsToCheckPattern = ipsToCheckPattern; + this.alternativeRemoteHostAttribute = alternativeRemoteHostAttribute; + } + + /** + * {@inheritDoc} + * Gets the remote ip from the request, and invokes spnego if it isn't filtered. + * + * @param context the request context + * @return {@link #yes()} if spnego should be invoked and ip isn't filtered, + * {@link #no()} otherwise. + */ + @Override + protected final Event doExecute(final RequestContext context) { + final String remoteIp = getRemoteIp(context); + logger.debug("Current user IP {}", remoteIp); + return shouldDoSpnego(remoteIp) ? yes() : no(); + } + + /** + * Default implementation -- simply check the IP filter. + * @param remoteIp the remote ip + * @return true boolean + */ + protected boolean shouldDoSpnego(final String remoteIp) { + return ipPatternCanBeChecked(remoteIp) && ipPatternMatches(remoteIp); + } + + /** + * Base class definition for whether the IP should be checked or not; overridable. + * @param remoteIp the remote ip + * @return whether or not the IP can / should be matched against the pattern + */ + protected boolean ipPatternCanBeChecked(final String remoteIp) { + return (this.ipsToCheckPattern != null && StringUtils.isNotBlank(remoteIp)); + } + + /** + * Simple pattern match to determine whether an IP should be checked. + * Could stand to be extended to support "real" IP addresses and patterns, but + * for the local / first implementation regex made more sense. + * @param remoteIp the remote ip + * @return whether the remote ip received should be queried + */ + protected boolean ipPatternMatches(final String remoteIp) { + final Matcher matcher = this.ipsToCheckPattern.matcher(remoteIp); + if (matcher.find()) { + logger.debug("Remote IP address {} should be checked based on the defined pattern {}", + remoteIp, this.ipsToCheckPattern.pattern()); + return true; + } + logger.debug("No pattern or remote IP defined, or pattern does not match remote IP [{}]", + remoteIp); + return false; + } + + /** + * Pulls the remote IP from the current HttpServletRequest, or grabs the value + * for the specified alternative attribute (say, for proxied requests). Falls + * back to providing the "normal" remote address if no value can be retrieved + * from the specified alternative header value. + * @param context the context + * @return the remote ip + */ + private String getRemoteIp(@NotNull final RequestContext context) { + final HttpServletRequest request = WebUtils.getHttpServletRequest(context); + String userAddress = request.getRemoteAddr(); + logger.debug("Remote Address = {}", userAddress); + + if (StringUtils.isNotBlank(this.alternativeRemoteHostAttribute)) { + + userAddress = request.getHeader(this.alternativeRemoteHostAttribute); + logger.debug("Header Attribute [{}] = [{}]", this.alternativeRemoteHostAttribute, userAddress); + + if (StringUtils.isBlank(userAddress)) { + userAddress = request.getRemoteAddr(); + logger.warn("No value could be retrieved from the header [{}]. Falling back to [{}].", + this.alternativeRemoteHostAttribute, userAddress); + } + } + return userAddress; + } + + /** + * Alternative header to be used for retrieving the remote system IP address. + * @param alternativeRemoteHostAttribute the alternative remote host attribute + */ + public final void setAlternativeRemoteHostAttribute(@NotNull final String alternativeRemoteHostAttribute) { + this.alternativeRemoteHostAttribute = alternativeRemoteHostAttribute; + } + + /** + * Regular expression string to define IPs which should be considered. + * @param ipsToCheckPattern the ips to check as a regex pattern + */ + public final void setIpsToCheckPattern(@NotNull final String ipsToCheckPattern) { + this.ipsToCheckPattern = Pattern.compile(ipsToCheckPattern); + } + + + @Override + public final String toString() { + return new ToStringBuilder(this) + .append("ipsToCheckPattern", this.ipsToCheckPattern) + .append("alternativeRemoteHostAttribute", this.alternativeRemoteHostAttribute) + .append("timeout", this.timeout) + .toString(); + } + + /** + * Set timeout (ms) for DNS requests; valuable for heterogeneous environments employing + * fall-through authentication mechanisms. + * @param timeout # of milliseconds to wait for a DNS request to return + */ + public final void setTimeout(final long timeout) { + this.timeout = timeout; + } + + /** + * Convenience method to perform a reverse DNS lookup. Threads the request + * through a custom Runnable class in order to prevent inordinately long + * user waits while performing reverse lookup. + * @param remoteIp the remote ip + * @return the remote host name + */ + protected String getRemoteHostName(final String remoteIp) { + final ReverseDNSRunnable revDNS = new ReverseDNSRunnable(remoteIp); + + final Thread t = new Thread(revDNS); + t.start(); + + try { + t.join(this.timeout); + } catch (final InterruptedException e) { + logger.debug("Threaded lookup failed. Defaulting to IP {}.", remoteIp, e); + } + + final String remoteHostName = revDNS.get(); + logger.debug("Found remote host name {}.", remoteHostName); + + return StringUtils.isNotEmpty(remoteHostName) ? remoteHostName : remoteIp; + } + + +} diff --git a/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/web/flow/client/HostNameSpnegoKnownClientSystemsFilterAction.java b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/web/flow/client/HostNameSpnegoKnownClientSystemsFilterAction.java new file mode 100644 index 000000000000..8bcb769cb1ca --- /dev/null +++ b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/web/flow/client/HostNameSpnegoKnownClientSystemsFilterAction.java @@ -0,0 +1,67 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.spnego.web.flow.client; + +import javax.validation.constraints.NotNull; +import java.util.regex.Pattern; + +/** + * A simple implementation of {@link BaseSpnegoKnownClientSystemsFilterAction} to allow / skip SPNEGO / KRB / + * NTLM authentication based on a regex match against a reverse DNS lookup of the requesting + * system. + * + * @author Sean Baker + * @author Misagh Moayyed + * @since 4.1 + */ +public class HostNameSpnegoKnownClientSystemsFilterAction extends BaseSpnegoKnownClientSystemsFilterAction { + private final Pattern hostNamePatternString; + + /** + * Instantiates a new hostname spnego known client systems filter action. + * + * @param hostNamePatternString the host name pattern string. + * The pattern to match the retrieved hostname against. + */ + public HostNameSpnegoKnownClientSystemsFilterAction(@NotNull final String hostNamePatternString) { + super(); + this.hostNamePatternString = Pattern.compile(hostNamePatternString); + } + + /** + * {@inheritDoc}. + *

+ * Checks whether the IP should even be paid attention to, + * then does a reverse DNS lookup, and if it matches the supplied pattern, performs SPNEGO + * else skips the process. + * + * @param remoteIp The remote ip address to validate + */ + @Override + protected boolean shouldDoSpnego(final String remoteIp) { + final boolean ipCheck = ipPatternCanBeChecked(remoteIp); + if(ipCheck && !ipPatternMatches(remoteIp)) { + return false; + } + final String hostName = getRemoteHostName(remoteIp); + logger.debug("Retrieved host name for the remote ip is {}", hostName); + return this.hostNamePatternString.matcher(hostName).find(); + } +} diff --git a/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/web/flow/client/LdapSpnegoKnownClientSystemsFilterAction.java b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/web/flow/client/LdapSpnegoKnownClientSystemsFilterAction.java new file mode 100644 index 000000000000..3d32bfdc54f8 --- /dev/null +++ b/cas-server-support-spnego/src/main/java/org/jasig/cas/support/spnego/web/flow/client/LdapSpnegoKnownClientSystemsFilterAction.java @@ -0,0 +1,173 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.spnego.web.flow.client; + +import org.apache.commons.lang3.StringUtils; +import org.ldaptive.Connection; +import org.ldaptive.ConnectionFactory; +import org.ldaptive.LdapAttribute; +import org.ldaptive.LdapEntry; +import org.ldaptive.LdapException; +import org.ldaptive.Response; +import org.ldaptive.ResultCode; +import org.ldaptive.SearchOperation; +import org.ldaptive.SearchRequest; +import org.ldaptive.SearchResult; +import org.ldaptive.Operation; + +import javax.validation.constraints.NotNull; + +/** + * Peek into an LDAP server and check for the existence of an attribute + * in order to target invocation of spnego. + * @author Misagh Moayyed + * @author Sean Baker + * @since 4.1 + */ +public class LdapSpnegoKnownClientSystemsFilterAction extends BaseSpnegoKnownClientSystemsFilterAction { + /** Attribute name in LDAP to indicate spnego invocation. **/ + public static final String DEFAULT_SPNEGO_ATTRIBUTE = "distinguishedName"; + + /** The must-have attribute name.*/ + protected final String spnegoAttributeName; + + /** The connection configuration to ldap.*/ + protected final ConnectionFactory connectionFactory; + + /** The search request. */ + protected final SearchRequest searchRequest; + + /** + * Instantiates new action. Initializes the default attribute + * to be {@link #DEFAULT_SPNEGO_ATTRIBUTE}. + * @param connectionFactory the connection factory + * @param searchRequest the search request + */ + public LdapSpnegoKnownClientSystemsFilterAction(@NotNull final ConnectionFactory connectionFactory, + @NotNull final SearchRequest searchRequest) { + this(connectionFactory, searchRequest, DEFAULT_SPNEGO_ATTRIBUTE); + } + + /** + * Instantiates a new action. + * + * @param connectionFactory the connection factory + * @param searchRequest the search request + * @param spnegoAttributeName the certificate revocation list attribute name + */ + public LdapSpnegoKnownClientSystemsFilterAction( + @NotNull final ConnectionFactory connectionFactory, + @NotNull final SearchRequest searchRequest, + @NotNull final String spnegoAttributeName) { + this.connectionFactory = connectionFactory; + this.spnegoAttributeName = spnegoAttributeName; + this.searchRequest = searchRequest; + } + + /** + * Create and open a connection to ldap + * via the given config and provider. + * + * @return the connection + * @throws LdapException the ldap exception + */ + protected Connection createConnection() throws LdapException { + logger.debug("Establishing a connection..."); + final Connection connection = this.connectionFactory.getConnection(); + connection.open(); + return connection; + } + + @Override + protected boolean shouldDoSpnego(final String remoteIp) { + final boolean ipCheck = ipPatternCanBeChecked(remoteIp); + if (ipCheck && !ipPatternMatches(remoteIp)) { + return false; + } + + return executeSearchForSpnegoAttribute(remoteIp); + } + + /** + * Searches the ldap instance for the attribute value. + * + * @param remoteIp the remote ip + * @return the boolean + */ + protected boolean executeSearchForSpnegoAttribute(final String remoteIp) { + Connection connection = null; + final String remoteHostName = getRemoteHostName(remoteIp); + logger.debug("Resolved remote hostname {} based on ip {}", + remoteHostName, remoteIp); + + try { + connection = createConnection(); + final Operation searchOperation = new SearchOperation(connection); + this.searchRequest.getSearchFilter().setParameter(0, remoteHostName); + + logger.debug("Using search filter {} on baseDn {}", + this.searchRequest.getSearchFilter().format(), + this.searchRequest.getBaseDn()); + + final Response searchResult = searchOperation.execute(this.searchRequest); + if (searchResult.getResultCode() == ResultCode.SUCCESS) { + return processSpnegoAttribute(searchResult); + } + throw new RuntimeException("Failed to establish a connection ldap. " + + searchResult.getMessage()); + + } catch (final LdapException e) { + logger.error(e.getMessage(), e); + throw new RuntimeException(e); + } finally { + if (connection != null) { + connection.close(); + } + } + } + + /** + * Verify spnego attribute value. + * + * @param searchResult the search result + * @return true if attribute value exists and has a value + */ + protected boolean processSpnegoAttribute(final Response searchResult) { + final SearchResult result = searchResult.getResult(); + + if (result == null || result.getEntries().isEmpty()) { + logger.debug("Spnego attribute is not found in the search results"); + return false; + } + final LdapEntry entry = result.getEntry(); + final LdapAttribute attribute = entry.getAttribute(this.spnegoAttributeName); + return verifySpnegyAttributeValue(attribute); + } + + /** + * Verify spnegy attribute value. + * This impl simply makes sure the attribute exists and has a value. + * @param attribute the ldap attribute + * @return true if available. false otherwise. + */ + protected boolean verifySpnegyAttributeValue(final LdapAttribute attribute) { + return attribute != null && StringUtils.isNotBlank(attribute.getStringValue()); + } +} diff --git a/cas-server-support-spnego/src/site/site.xml b/cas-server-support-spnego/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-support-spnego/src/site/site.xml @@ -0,0 +1,28 @@ + + + + +

+ + + + diff --git a/cas-server-3.4.2/cas-server-support-radius/src/test/clover/clover.license b/cas-server-support-spnego/src/test/clover/clover.license similarity index 100% rename from cas-server-3.4.2/cas-server-support-radius/src/test/clover/clover.license rename to cas-server-support-spnego/src/test/clover/clover.license diff --git a/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/MockJCSIFAuthentication.java b/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/MockJCSIFAuthentication.java new file mode 100644 index 000000000000..91b67ad6be62 --- /dev/null +++ b/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/MockJCSIFAuthentication.java @@ -0,0 +1,66 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.spnego; + +import jcifs.spnego.Authentication; +import jcifs.spnego.AuthenticationException; + +import java.security.Principal; + +/** + * + * @author Marc-Antoine Garrigue + * @author Arnaud Lesueur + * @since 3.1 + * @deprecated As of 4.1, the class name is abbreviated in a way that is not per camel-casing standards and will be renamed in the future. + */ +@Deprecated +public class MockJCSIFAuthentication extends Authentication { + private final Principal principal; + + private final boolean valid; + + private final byte[] outToken = new byte[] {4, 5, 6}; + + public MockJCSIFAuthentication(final boolean valid) { + this.principal = new MockPrincipal("test"); + this.valid = valid; + + } + + @Override + public byte[] getNextToken() { + + return this.valid ? this.outToken : null; + } + + @Override + public java.security.Principal getPrincipal() { + + return this.valid ? this.principal : null; + } + + @Override + public void process(final byte[] arg0) throws AuthenticationException { + if (!this.valid) { + throw new AuthenticationException("not valid"); + } + } + +} diff --git a/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/MockPrincipal.java b/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/MockPrincipal.java new file mode 100644 index 000000000000..a507eb0f9755 --- /dev/null +++ b/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/MockPrincipal.java @@ -0,0 +1,42 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.spnego; + +import java.security.Principal; + +/** + * @author Marc-Antoine Garrigue + * @author Arnaud Lesueur + * @since 3.1 + */ +public class MockPrincipal implements Principal { + + private final String principal; + + public MockPrincipal(final String principal) { + super(); + this.principal = principal; + } + + @Override + public String getName() { + return this.principal; + } + +} diff --git a/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/authentication/handler/support/JCSIFSpnegoAuthenticationHandlerTests.java b/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/authentication/handler/support/JCSIFSpnegoAuthenticationHandlerTests.java new file mode 100644 index 000000000000..27b473dff1ab --- /dev/null +++ b/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/authentication/handler/support/JCSIFSpnegoAuthenticationHandlerTests.java @@ -0,0 +1,112 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.spnego.authentication.handler.support; + +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.jasig.cas.authentication.principal.PrincipalFactory; +import org.jasig.cas.support.spnego.MockJCSIFAuthentication; +import org.jasig.cas.support.spnego.authentication.principal.SpnegoCredential; +import org.junit.Before; +import org.junit.Test; + +import java.security.GeneralSecurityException; + +import static org.junit.Assert.*; + +/** + * @author Marc-Antoine Garrigue + * @author Arnaud Lesueur + * @since 3.1 + * @deprecated As of 4.1, the class name is abbreviated in a way that is not per camel-casing standards and will be renamed in the future. + */ +@Deprecated +public class JCSIFSpnegoAuthenticationHandlerTests { + private JCIFSSpnegoAuthenticationHandler authenticationHandler; + + @Before + public void setUp() throws Exception { + this.authenticationHandler = new JCIFSSpnegoAuthenticationHandler(); + } + + @Test + public void verifySuccessfulAuthenticationWithDomainName() throws Exception { + final SpnegoCredential credentials = new SpnegoCredential(new byte[] {0, 1, 2}); + this.authenticationHandler.setPrincipalWithDomainName(true); + this.authenticationHandler.setAuthentication(new MockJCSIFAuthentication(true)); + assertNotNull(this.authenticationHandler.authenticate(credentials)); + assertEquals("test", credentials.getPrincipal().getId()); + assertNotNull(credentials.getNextToken()); + } + + @Test + public void verifySuccessfulAuthenticationWithoutDomainName() throws Exception { + final SpnegoCredential credentials = new SpnegoCredential(new byte[] {0, 1, 2}); + this.authenticationHandler.setPrincipalWithDomainName(false); + this.authenticationHandler.setAuthentication(new MockJCSIFAuthentication(true)); + assertNotNull(this.authenticationHandler.authenticate(credentials)); + assertEquals("test", credentials.getPrincipal().getId()); + assertNotNull(credentials.getNextToken()); + } + + @Test + public void verifyUnsuccessfulAuthentication() throws Exception { + final SpnegoCredential credentials = new SpnegoCredential(new byte[] {0, 1, 2}); + this.authenticationHandler.setAuthentication(new MockJCSIFAuthentication(false)); + try { + this.authenticationHandler.authenticate(credentials); + fail("An AuthenticationException should have been thrown"); + } catch (final GeneralSecurityException e) { + assertNull(credentials.getNextToken()); + assertNull(credentials.getPrincipal()); + } + } + + @Test + public void verifySupports() { + assertFalse(this.authenticationHandler.supports(null)); + assertTrue(this.authenticationHandler.supports(new SpnegoCredential(new byte[] {0, 1, 2}))); + assertFalse(this.authenticationHandler.supports(new UsernamePasswordCredential())); + } + + @Test + public void verifyGetSimpleCredentials() { + final String myNtlmUser = "DOMAIN\\Username"; + final String myNtlmUserWithNoDomain = "Username"; + final String myKerberosUser = "Username@DOMAIN.COM"; + + final PrincipalFactory factory = new DefaultPrincipalFactory(); + + this.authenticationHandler.setPrincipalWithDomainName(true); + assertEquals(factory.createPrincipal(myNtlmUser), this.authenticationHandler + .getPrincipal(myNtlmUser, true)); + assertEquals(factory.createPrincipal(myNtlmUserWithNoDomain), this.authenticationHandler + .getPrincipal(myNtlmUserWithNoDomain, false)); + assertEquals(factory.createPrincipal(myKerberosUser), this.authenticationHandler + .getPrincipal(myKerberosUser, false)); + + this.authenticationHandler.setPrincipalWithDomainName(false); + assertEquals(factory.createPrincipal("Username"), this.authenticationHandler + .getPrincipal(myNtlmUser, true)); + assertEquals(factory.createPrincipal("Username"), this.authenticationHandler + .getPrincipal(myNtlmUserWithNoDomain, true)); + assertEquals(factory.createPrincipal("Username"), this.authenticationHandler + .getPrincipal(myKerberosUser, false)); + } +} diff --git a/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/authentication/principal/SpnegoCredentialsTests.java b/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/authentication/principal/SpnegoCredentialsTests.java new file mode 100644 index 000000000000..1b1e5ae080d1 --- /dev/null +++ b/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/authentication/principal/SpnegoCredentialsTests.java @@ -0,0 +1,78 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.spnego.authentication.principal; + +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.jasig.cas.authentication.principal.Principal; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.*; + + +/** + * @author Misagh Moayyed + * @since 3.0.0 + */ +public class SpnegoCredentialsTests { + + @Test + public void verifyToStringWithNoPrincipal() { + final SpnegoCredential credentials = new SpnegoCredential(new byte[] {}); + + assertTrue(credentials.toString().contains("unknown")); + } + + @Test + public void verifyToStringWithPrincipal() { + final SpnegoCredential credentials = new SpnegoCredential(new byte[] {}); + final Principal principal = new DefaultPrincipalFactory().createPrincipal("test"); + credentials.setPrincipal(principal); + assertEquals("test", credentials.toString()); + } + + /** + * Important for SPNEGO in particular as the credential will be hashed prior to Principal resolution + */ + @Test + public void verifyCredentialsHashSafelyWithoutPrincipal() { + final SpnegoCredential credential = new SpnegoCredential(new byte[] {}); + final Set set = new HashSet<>(); + try { + set.add(credential); + } catch(final Exception e) { + fail(e.getMessage()); + } + } + + /** + * Make sure that when the Principal becomes populated / changes we return a new hash + */ + @Test + public void verifyPrincipalAffectsHash(){ + final SpnegoCredential credential = new SpnegoCredential(new byte[] {}); + final int hash1 = credential.hashCode(); + final Principal principal = new DefaultPrincipalFactory().createPrincipal("test"); + credential.setPrincipal(principal); + final int hash2 = credential.hashCode(); + assertNotEquals(hash1, hash2); + } +} diff --git a/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/authentication/principal/SpnegoCredentialsToPrincipalResolverTests.java b/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/authentication/principal/SpnegoCredentialsToPrincipalResolverTests.java new file mode 100644 index 000000000000..11d6cae8b1ba --- /dev/null +++ b/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/authentication/principal/SpnegoCredentialsToPrincipalResolverTests.java @@ -0,0 +1,58 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.support.spnego.authentication.principal; + +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Marc-Antoine Garrigue + * @author Arnaud Lesueur + * @since 3.1 + * + */ +public class SpnegoCredentialsToPrincipalResolverTests { + private SpnegoPrincipalResolver resolver; + + private SpnegoCredential spnegoCredentials; + + @Before + public void setUp() throws Exception { + this.resolver = new SpnegoPrincipalResolver(); + this.spnegoCredentials = new SpnegoCredential(new byte[] {0, 1, 2}); + } + + @Test + public void verifyValidCredentials() { + this.spnegoCredentials.setPrincipal(new DefaultPrincipalFactory().createPrincipal("test")); + assertEquals("test", this.resolver.resolve(this.spnegoCredentials) + .getId()); + } + + @Test + public void verifySupports() { + assertFalse(this.resolver.supports(null)); + assertTrue(this.resolver.supports(this.spnegoCredentials)); + assertFalse(this.resolver.supports(new UsernamePasswordCredential())); + } +} diff --git a/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/web/flow/client/AllSpnegoKnownClientSystemsFilterActionTest.java b/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/web/flow/client/AllSpnegoKnownClientSystemsFilterActionTest.java new file mode 100644 index 000000000000..b954df9cfb05 --- /dev/null +++ b/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/web/flow/client/AllSpnegoKnownClientSystemsFilterActionTest.java @@ -0,0 +1,149 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.spnego.web.flow.client; + +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.webflow.action.EventFactorySupport; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.test.MockRequestContext; + +import static org.junit.Assert.*; + +/** + * Test cases for {@link BaseSpnegoKnownClientSystemsFilterAction} + * and {@link HostNameSpnegoKnownClientSystemsFilterAction}. + * @author Sean Baker sean.baker@usuhs.edu + * @author Misagh Moayyed + * @since 4.1 + */ +public class AllSpnegoKnownClientSystemsFilterActionTest { + + @Test + public void ensureRemoteIpShouldBeChecked() { + final BaseSpnegoKnownClientSystemsFilterAction action = + new BaseSpnegoKnownClientSystemsFilterAction("^192\\.158\\..+"); + + final MockRequestContext ctx = new MockRequestContext(); + final MockHttpServletRequest req = new MockHttpServletRequest(); + req.setRemoteAddr("192.158.5.781"); + final ServletExternalContext extCtx = new ServletExternalContext( + new MockServletContext(), req, + new MockHttpServletResponse()); + ctx.setExternalContext(extCtx); + + final Event ev = action.doExecute(ctx); + assertEquals(ev.getId(), new EventFactorySupport().yes(this).getId()); + } + + @Test + public void ensureRemoteIpShouldNotBeChecked() { + final BaseSpnegoKnownClientSystemsFilterAction action = + new BaseSpnegoKnownClientSystemsFilterAction("^192\\.158\\..+"); + + final MockRequestContext ctx = new MockRequestContext(); + final MockHttpServletRequest req = new MockHttpServletRequest(); + req.setRemoteAddr("193.158.5.781"); + final ServletExternalContext extCtx = new ServletExternalContext( + new MockServletContext(), req, + new MockHttpServletResponse()); + ctx.setExternalContext(extCtx); + + final Event ev = action.doExecute(ctx); + assertNotEquals(ev.getId(), new EventFactorySupport().yes(this).getId()); + } + + @Test + public void ensureAltRemoteIpHeaderShouldBeChecked() { + final BaseSpnegoKnownClientSystemsFilterAction action = + new BaseSpnegoKnownClientSystemsFilterAction("^74\\.125\\..+", "alternateRemoteIp"); + + final MockRequestContext ctx = new MockRequestContext(); + final MockHttpServletRequest req = new MockHttpServletRequest(); + req.setRemoteAddr("555.555.555.555"); + req.addHeader("alternateRemoteIp", "74.125.136.102"); + final ServletExternalContext extCtx = new ServletExternalContext( + new MockServletContext(), req, + new MockHttpServletResponse()); + ctx.setExternalContext(extCtx); + + final Event ev = action.doExecute(ctx); + assertEquals(ev.getId(), new EventFactorySupport().yes(this).getId()); + } + + @Test + public void ensureHostnameShouldDoSpnego() { + final HostNameSpnegoKnownClientSystemsFilterAction action = + new HostNameSpnegoKnownClientSystemsFilterAction("\\w+\\.\\w+\\.\\w+"); + + final MockRequestContext ctx = new MockRequestContext(); + final MockHttpServletRequest req = new MockHttpServletRequest(); + req.setRemoteAddr("74.125.136.102"); + final ServletExternalContext extCtx = new ServletExternalContext( + new MockServletContext(), req, + new MockHttpServletResponse()); + ctx.setExternalContext(extCtx); + + final Event ev = action.doExecute(ctx); + assertEquals(ev.getId(), new EventFactorySupport().yes(this).getId()); + + } + + @Test + public void ensureHostnameAndIpShouldDoSpnego() { + final HostNameSpnegoKnownClientSystemsFilterAction action = + new HostNameSpnegoKnownClientSystemsFilterAction("\\w+\\.\\w+\\.\\w+"); + action.setIpsToCheckPattern("74\\..+"); + + final MockRequestContext ctx = new MockRequestContext(); + final MockHttpServletRequest req = new MockHttpServletRequest(); + req.setRemoteAddr("74.125.136.102"); + final ServletExternalContext extCtx = new ServletExternalContext( + new MockServletContext(), req, + new MockHttpServletResponse()); + ctx.setExternalContext(extCtx); + + final Event ev = action.doExecute(ctx); + assertEquals(ev.getId(), new EventFactorySupport().yes(this).getId()); + + } + + @Test + public void verifyIpMismatchWhenCheckingHostnameForSpnego() { + final HostNameSpnegoKnownClientSystemsFilterAction action = + new HostNameSpnegoKnownClientSystemsFilterAction("\\w+\\.\\w+\\.\\w+"); + action.setIpsToCheckPattern("14\\..+"); + + final MockRequestContext ctx = new MockRequestContext(); + final MockHttpServletRequest req = new MockHttpServletRequest(); + req.setRemoteAddr("74.125.136.102"); + final ServletExternalContext extCtx = new ServletExternalContext( + new MockServletContext(), req, + new MockHttpServletResponse()); + ctx.setExternalContext(extCtx); + + final Event ev = action.doExecute(ctx); + assertEquals(ev.getId(), new EventFactorySupport().no(this).getId()); + + } +} diff --git a/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/web/flow/client/LdapSpnegoKnownClientSystemsFilterActionTests.java b/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/web/flow/client/LdapSpnegoKnownClientSystemsFilterActionTests.java new file mode 100644 index 000000000000..66e02cddf882 --- /dev/null +++ b/cas-server-support-spnego/src/test/java/org/jasig/cas/support/spnego/web/flow/client/LdapSpnegoKnownClientSystemsFilterActionTests.java @@ -0,0 +1,79 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.support.spnego.web.flow.client; + +import org.jasig.cas.adaptors.ldap.AbstractLdapTests; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ldaptive.ConnectionFactory; +import org.ldaptive.SearchRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.webflow.action.EventFactorySupport; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.test.MockRequestContext; + +import static org.junit.Assert.assertEquals; + +/** + * Test cases for {@link LdapSpnegoKnownClientSystemsFilterAction}. + * @author Misagh Moayyed + * @since 4.1 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration({"/ldap-context.xml"}) +public class LdapSpnegoKnownClientSystemsFilterActionTests extends AbstractLdapTests { + + @Autowired + @Qualifier("provisioningConnectionFactory") + private ConnectionFactory connectionFactory; + + @Autowired + private SearchRequest searchRequest; + + @BeforeClass + public static void bootstrap() throws Exception { + initDirectoryServer(); + } + + @Test + public void ensureLdapAttributeShouldDoSpnego() { + final LdapSpnegoKnownClientSystemsFilterAction action = + new LdapSpnegoKnownClientSystemsFilterAction(this.connectionFactory, + this.searchRequest, "mail"); + final MockRequestContext ctx = new MockRequestContext(); + final MockHttpServletRequest req = new MockHttpServletRequest(); + req.setRemoteAddr("127.0.0.1"); + final ServletExternalContext extCtx = new ServletExternalContext( + new MockServletContext(), req, + new MockHttpServletResponse()); + ctx.setExternalContext(extCtx); + + final Event ev = action.doExecute(ctx); + assertEquals(ev.getId(), new EventFactorySupport().yes(this).getId()); + } +} diff --git a/cas-server-support-spnego/src/test/resources/ldap-context.xml b/cas-server-support-spnego/src/test/resources/ldap-context.xml new file mode 100644 index 000000000000..12d31901b646 --- /dev/null +++ b/cas-server-support-spnego/src/test/resources/ldap-context.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-support-spnego/src/test/resources/ldap.properties b/cas-server-support-spnego/src/test/resources/ldap.properties new file mode 100644 index 000000000000..7d4729d4b5fb --- /dev/null +++ b/cas-server-support-spnego/src/test/resources/ldap.properties @@ -0,0 +1,68 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +#======================================== +# General properties +#======================================== +ldap.url=ldap://localhost:1389 + +# Start TLS for SSL connections +ldap.useStartTLS=false + +# Directory root DN +ldap.rootDn=dc=example,dc=org + +# Base DN of users to be authenticated +ldap.baseDn=ou=people,dc=example,dc=org + +# LDAP connection timeout in milliseconds +ldap.connectTimeout=3000 + +# Manager credential DN +ldap.managerDn=cn=Directory Manager,dc=example,dc=org + +# Manager credential password +ldap.managerPassword=Password + +#======================================== +# LDAP connection pool configuration +#======================================== +ldap.pool.minSize=1 +ldap.pool.maxSize=10 +ldap.pool.validateOnCheckout=false +ldap.pool.validatePeriodically=true + +# Amount of time in milliseconds to block on pool exhausted condition +# before giving up. +ldap.pool.blockWaitTime=3000 + +# Frequency of connection validation in seconds +# Only applies if validatePeriodically=true +ldap.pool.validatePeriod=300 + +# Attempt to prune connections every N seconds +ldap.pool.prunePeriod=300 + +# Maximum amount of time an idle connection is allowed to be in +# pool before it is liable to be removed/destroyed +ldap.pool.idleTime=600 + + +# Ldap domain used to resolve dn +ldap.domain=example.org diff --git a/cas-server-support-spnego/src/test/resources/log4j2.xml b/cas-server-support-spnego/src/test/resources/log4j2.xml new file mode 100644 index 000000000000..06932c288c3c --- /dev/null +++ b/cas-server-support-spnego/src/test/resources/log4j2.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-3.4.2/cas-server-support-radius/.cvsignore b/cas-server-support-trusted/.cvsignore similarity index 100% rename from cas-server-3.4.2/cas-server-support-radius/.cvsignore rename to cas-server-support-trusted/.cvsignore diff --git a/cas-server-support-trusted/NOTICE b/cas-server-support-trusted/NOTICE new file mode 100644 index 000000000000..8035593a6fab --- /dev/null +++ b/cas-server-support-trusted/NOTICE @@ -0,0 +1,101 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS Trusted User Support under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + CDI APIs under Apache License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-support-trusted/pom.xml b/cas-server-support-trusted/pom.xml new file mode 100644 index 000000000000..45a6a2436180 --- /dev/null +++ b/cas-server-support-trusted/pom.xml @@ -0,0 +1,50 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-support-trusted + jar + Apereo CAS Trusted User Support + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + org.springframework.webflow + spring-webflow + compile + + + + + + ${project.parent.basedir} + + diff --git a/cas-server-support-trusted/src/main/java/org/jasig/cas/adaptors/trusted/authentication/handler/support/PrincipalBearingCredentialsAuthenticationHandler.java b/cas-server-support-trusted/src/main/java/org/jasig/cas/adaptors/trusted/authentication/handler/support/PrincipalBearingCredentialsAuthenticationHandler.java new file mode 100644 index 000000000000..0cf35b4d7391 --- /dev/null +++ b/cas-server-support-trusted/src/main/java/org/jasig/cas/adaptors/trusted/authentication/handler/support/PrincipalBearingCredentialsAuthenticationHandler.java @@ -0,0 +1,56 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.trusted.authentication.handler.support; + +import org.jasig.cas.adaptors.trusted.authentication.principal.PrincipalBearingCredential; +import org.jasig.cas.authentication.AbstractAuthenticationHandler; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.HandlerResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.security.GeneralSecurityException; + +/** + * AuthenticationHandler which authenticates Principal-bearing credentials. + * Authentication must have occurred at the time the Principal-bearing + * credentials were created, so we perform no further authentication. Thus + * merely by being presented a PrincipalBearingCredential, this handler returns + * true. + * + * @author Andrew Petro + * @since 3.0.0.5 + */ +public final class PrincipalBearingCredentialsAuthenticationHandler extends AbstractAuthenticationHandler { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public HandlerResult authenticate(final Credential credential) throws GeneralSecurityException { + logger.debug("Trusting credential for: {}", credential); + return new DefaultHandlerResult( + this, (PrincipalBearingCredential) credential, this.principalFactory.createPrincipal(credential.getId())); + } + + @Override + public boolean supports(final Credential credential) { + return credential instanceof PrincipalBearingCredential; + } +} diff --git a/cas-server-support-trusted/src/main/java/org/jasig/cas/adaptors/trusted/authentication/principal/PrincipalBearingCredential.java b/cas-server-support-trusted/src/main/java/org/jasig/cas/adaptors/trusted/authentication/principal/PrincipalBearingCredential.java new file mode 100644 index 000000000000..ca83a1a892bc --- /dev/null +++ b/cas-server-support-trusted/src/main/java/org/jasig/cas/adaptors/trusted/authentication/principal/PrincipalBearingCredential.java @@ -0,0 +1,71 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.trusted.authentication.principal; + +import org.jasig.cas.authentication.AbstractCredential; +import org.jasig.cas.authentication.principal.Principal; +import org.springframework.util.Assert; + +/** + * Credential that bear the fully resolved and authenticated Principal, or an + * indication that there is no such Principal. These Credential are a mechanism + * to pass into CAS information about an authentication and Principal resolution + * that has already happened in layers in front of CAS, e.g. by means of a Java + * Servlet Filter or by means of container authentication in the servlet + * container or Apache layers. DO NOT accept these Credential from arbitrary + * web-servicey calls to CAS. Rather, the code constructing these Credential + * must be trusted to perform appropriate authentication before issuing these + * credentials. + * + * @author Andrew Petro + * @since 3.0.0.5 + */ +public final class PrincipalBearingCredential extends AbstractCredential { + + /** Serialization version marker. */ + private static final long serialVersionUID = 8866786438439775669L; + + /** The trusted principal. */ + private final Principal principal; + + /** + * Instantiates a new principal bearing credential. + * + * @param principal the principal + */ + public PrincipalBearingCredential(final Principal principal) { + Assert.notNull(principal, "principal cannot be null"); + this.principal = principal; + } + + /** + * Get the previously authenticated Principal. + * + * @return authenticated Principal + */ + public Principal getPrincipal() { + return this.principal; + } + + @Override + public String getId() { + return this.principal.getId(); + } + +} diff --git a/cas-server-support-trusted/src/main/java/org/jasig/cas/adaptors/trusted/authentication/principal/PrincipalBearingPrincipalResolver.java b/cas-server-support-trusted/src/main/java/org/jasig/cas/adaptors/trusted/authentication/principal/PrincipalBearingPrincipalResolver.java new file mode 100644 index 000000000000..c8711bba14d1 --- /dev/null +++ b/cas-server-support-trusted/src/main/java/org/jasig/cas/adaptors/trusted/authentication/principal/PrincipalBearingPrincipalResolver.java @@ -0,0 +1,43 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.trusted.authentication.principal; + +import org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver; +import org.jasig.cas.authentication.Credential; + +/** + * Extracts the Principal out of PrincipalBearingCredential. It is very simple + * to resolve PrincipalBearingCredential to a Principal since the credentials + * already bear the ready-to-go Principal. + * + * @author Andrew Petro + * @since 3.0.0.5 + */ +public final class PrincipalBearingPrincipalResolver extends PersonDirectoryPrincipalResolver { + + @Override + protected String extractPrincipalId(final Credential credential) { + return ((PrincipalBearingCredential) credential).getPrincipal().getId(); + } + + @Override + public boolean supports(final Credential credential) { + return credential instanceof PrincipalBearingCredential; + } +} diff --git a/cas-server-support-trusted/src/main/java/org/jasig/cas/adaptors/trusted/web/flow/PrincipalFromRequestRemoteUserNonInteractiveCredentialsAction.java b/cas-server-support-trusted/src/main/java/org/jasig/cas/adaptors/trusted/web/flow/PrincipalFromRequestRemoteUserNonInteractiveCredentialsAction.java new file mode 100644 index 000000000000..a780a3126552 --- /dev/null +++ b/cas-server-support-trusted/src/main/java/org/jasig/cas/adaptors/trusted/web/flow/PrincipalFromRequestRemoteUserNonInteractiveCredentialsAction.java @@ -0,0 +1,63 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.trusted.web.flow; + +import org.jasig.cas.adaptors.trusted.authentication.principal.PrincipalBearingCredential; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.web.flow.AbstractNonInteractiveCredentialsAction; +import org.jasig.cas.web.support.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; +import org.springframework.webflow.execution.RequestContext; + +import javax.servlet.http.HttpServletRequest; + +/** + * Implementation of the NonInteractiveCredentialsAction that looks for a remote + * user that is set in the HttpServletRequest and attempts to + * construct a Principal (and thus a PrincipalBearingCredential). If it doesn't + * find one, this class returns and error event which tells the web flow it + * could not find any credentials. + * + * @author Scott Battaglia + * @since 3.0.0.5 + */ +public final class PrincipalFromRequestRemoteUserNonInteractiveCredentialsAction + extends AbstractNonInteractiveCredentialsAction { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + protected Credential constructCredentialsFromRequest( + final RequestContext context) { + final HttpServletRequest request = WebUtils + .getHttpServletRequest(context); + final String remoteUser = request.getRemoteUser(); + + if (StringUtils.hasText(remoteUser)) { + logger.debug("Remote User [{}] found in HttpServletRequest", remoteUser); + return new PrincipalBearingCredential(this.principalFactory.createPrincipal(remoteUser)); + } + + logger.debug("Remote User not found in HttpServletRequest."); + + return null; + } +} diff --git a/cas-server-support-trusted/src/main/java/org/jasig/cas/adaptors/trusted/web/flow/PrincipalFromRequestUserPrincipalNonInteractiveCredentialsAction.java b/cas-server-support-trusted/src/main/java/org/jasig/cas/adaptors/trusted/web/flow/PrincipalFromRequestUserPrincipalNonInteractiveCredentialsAction.java new file mode 100644 index 000000000000..08a10bf73faf --- /dev/null +++ b/cas-server-support-trusted/src/main/java/org/jasig/cas/adaptors/trusted/web/flow/PrincipalFromRequestUserPrincipalNonInteractiveCredentialsAction.java @@ -0,0 +1,63 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.trusted.web.flow; + +import org.jasig.cas.adaptors.trusted.authentication.principal.PrincipalBearingCredential; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.web.flow.AbstractNonInteractiveCredentialsAction; +import org.jasig.cas.web.support.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.webflow.execution.RequestContext; + +import javax.servlet.http.HttpServletRequest; +import java.security.Principal; + +/** + * Implementation of the NonInteractiveCredentialsAction that looks for a user + * principal that is set in the HttpServletRequest and attempts + * to construct a Principal (and thus a PrincipalBearingCredential). If it + * doesn't find one, this class returns and error event which tells the web flow + * it could not find any credentials. + * + * @author Scott Battaglia + * @since 3.0.0.5 + */ +public final class PrincipalFromRequestUserPrincipalNonInteractiveCredentialsAction + extends AbstractNonInteractiveCredentialsAction { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + protected Credential constructCredentialsFromRequest( + final RequestContext context) { + final HttpServletRequest request = WebUtils + .getHttpServletRequest(context); + final Principal principal = request.getUserPrincipal(); + + if (principal != null) { + + logger.debug("UserPrincipal [{}] found in HttpServletRequest", principal.getName()); + return new PrincipalBearingCredential(this.principalFactory.createPrincipal(principal.getName())); + } + + logger.debug("UserPrincipal not found in HttpServletRequest."); + return null; + } +} diff --git a/cas-server-support-trusted/src/site/site.xml b/cas-server-support-trusted/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-support-trusted/src/site/site.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/cas-server-3.4.2/cas-server-support-spnego/src/test/clover/clover.license b/cas-server-support-trusted/src/test/clover/clover.license similarity index 100% rename from cas-server-3.4.2/cas-server-support-spnego/src/test/clover/clover.license rename to cas-server-support-trusted/src/test/clover/clover.license diff --git a/cas-server-support-trusted/src/test/java/org/jasig/cas/adaptors/trusted/authentication/handler/support/PrincipalBearingCredentialsAuthenticationHandlerTests.java b/cas-server-support-trusted/src/test/java/org/jasig/cas/adaptors/trusted/authentication/handler/support/PrincipalBearingCredentialsAuthenticationHandlerTests.java new file mode 100644 index 000000000000..fa8330d5bf14 --- /dev/null +++ b/cas-server-support-trusted/src/test/java/org/jasig/cas/adaptors/trusted/authentication/handler/support/PrincipalBearingCredentialsAuthenticationHandlerTests.java @@ -0,0 +1,53 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.trusted.authentication.handler.support; + +import org.jasig.cas.adaptors.trusted.authentication.principal.PrincipalBearingCredential; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Andrew Petro + * @since 3.0.0.5 + */ +public final class PrincipalBearingCredentialsAuthenticationHandlerTests { + + private final PrincipalBearingCredentialsAuthenticationHandler handler + = new PrincipalBearingCredentialsAuthenticationHandler(); + /** + * When the credentials bear a Principal, succeed the authentication. + */ + @Test + public void verifyNonNullPrincipal() throws Exception { + final PrincipalBearingCredential credentials = new PrincipalBearingCredential( + new DefaultPrincipalFactory().createPrincipal("scott")); + assertNotNull(this.handler.authenticate(credentials)); + } + + @Test + public void verifySupports() { + final PrincipalBearingCredential credentials = + new PrincipalBearingCredential(new DefaultPrincipalFactory().createPrincipal("scott")); + assertTrue(this.handler.supports(credentials)); + assertFalse(this.handler.supports(new UsernamePasswordCredential())); + } +} diff --git a/cas-server-support-trusted/src/test/java/org/jasig/cas/adaptors/trusted/authentication/principal/PrincipalBearingCredentialsTests.java b/cas-server-support-trusted/src/test/java/org/jasig/cas/adaptors/trusted/authentication/principal/PrincipalBearingCredentialsTests.java new file mode 100644 index 000000000000..1a676a7510c5 --- /dev/null +++ b/cas-server-support-trusted/src/test/java/org/jasig/cas/adaptors/trusted/authentication/principal/PrincipalBearingCredentialsTests.java @@ -0,0 +1,44 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.trusted.authentication.principal; + +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @since 3.0.0.5 + */ +public class PrincipalBearingCredentialsTests { + + private PrincipalBearingCredential principalBearingCredentials; + + @Before + public void setUp() throws Exception { + this.principalBearingCredentials = new PrincipalBearingCredential(new DefaultPrincipalFactory().createPrincipal("test")); + } + + @Test + public void verifyGetOfPrincipal() { + assertEquals("test", this.principalBearingCredentials.getPrincipal().getId()); + } +} diff --git a/cas-server-support-trusted/src/test/java/org/jasig/cas/adaptors/trusted/authentication/principal/PrincipalBearingCredentialsToPrincipalResolverTests.java b/cas-server-support-trusted/src/test/java/org/jasig/cas/adaptors/trusted/authentication/principal/PrincipalBearingCredentialsToPrincipalResolverTests.java new file mode 100644 index 000000000000..179418208ee0 --- /dev/null +++ b/cas-server-support-trusted/src/test/java/org/jasig/cas/adaptors/trusted/authentication/principal/PrincipalBearingCredentialsToPrincipalResolverTests.java @@ -0,0 +1,53 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.trusted.authentication.principal; + +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @since 3.0.0.5 + */ +public class PrincipalBearingCredentialsToPrincipalResolverTests { + private PrincipalBearingPrincipalResolver resolver; + + @Before + public void setUp() throws Exception { + this.resolver = new PrincipalBearingPrincipalResolver(); + } + + @Test + public void verifySupports() { + assertTrue(this.resolver.supports(new PrincipalBearingCredential(new DefaultPrincipalFactory().createPrincipal("test")))); + assertFalse(this.resolver.supports(new UsernamePasswordCredential())); + assertFalse(this.resolver.supports(null)); + } + + @Test + public void verifyReturnedPrincipal() { + assertEquals("test", this.resolver.resolve( + new PrincipalBearingCredential(new DefaultPrincipalFactory().createPrincipal("test"))).getId()); + } + +} diff --git a/cas-server-support-trusted/src/test/java/org/jasig/cas/adaptors/trusted/web/flow/PrincipalFromRequestRemoteUserNonInteractiveCredentialsActionTests.java b/cas-server-support-trusted/src/test/java/org/jasig/cas/adaptors/trusted/web/flow/PrincipalFromRequestRemoteUserNonInteractiveCredentialsActionTests.java new file mode 100644 index 000000000000..330d891c2e1e --- /dev/null +++ b/cas-server-support-trusted/src/test/java/org/jasig/cas/adaptors/trusted/web/flow/PrincipalFromRequestRemoteUserNonInteractiveCredentialsActionTests.java @@ -0,0 +1,98 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.trusted.web.flow; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.jasig.cas.CentralAuthenticationServiceImpl; +import org.jasig.cas.adaptors.trusted.authentication.handler.support.PrincipalBearingCredentialsAuthenticationHandler; +import org.jasig.cas.adaptors.trusted.authentication.principal.PrincipalBearingPrincipalResolver; +import org.jasig.cas.authentication.AuthenticationHandler; +import org.jasig.cas.authentication.AuthenticationManager; +import org.jasig.cas.authentication.PolicyBasedAuthenticationManager; +import org.jasig.cas.authentication.principal.PrincipalResolver; +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.logout.LogoutManager; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.ticket.registry.DefaultTicketRegistry; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.test.MockRequestContext; + +/** + * @author Scott Battaglia + * @since 3.0.0.5 + * + */ +public class PrincipalFromRequestRemoteUserNonInteractiveCredentialsActionTests { + + private PrincipalFromRequestRemoteUserNonInteractiveCredentialsAction action; + + @Before + public void setUp() throws Exception { + this.action = new PrincipalFromRequestRemoteUserNonInteractiveCredentialsAction(); + + final Map idGenerators = new HashMap<>(); + idGenerators.put(SimpleWebApplicationServiceImpl.class.getName(), new DefaultUniqueTicketIdGenerator()); + + + final AuthenticationManager authenticationManager = new PolicyBasedAuthenticationManager( + Collections.singletonMap( + new PrincipalBearingCredentialsAuthenticationHandler(), + new PrincipalBearingPrincipalResolver())); + final CentralAuthenticationServiceImpl centralAuthenticationService = new CentralAuthenticationServiceImpl( + new DefaultTicketRegistry(), null, authenticationManager, new DefaultUniqueTicketIdGenerator(), + idGenerators, new NeverExpiresExpirationPolicy(), new NeverExpiresExpirationPolicy(), + mock(ServicesManager.class), mock(LogoutManager.class)); + this.action.setCentralAuthenticationService(centralAuthenticationService); + } + + @Test + public void verifyRemoteUserExists() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRemoteUser("test"); + + final MockRequestContext context = new MockRequestContext(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + + assertEquals("success", this.action.execute(context).getId()); + } + + @Test + public void verifyRemoteUserDoesntExists() throws Exception { + final MockRequestContext context = new MockRequestContext(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), new MockHttpServletRequest(), new MockHttpServletResponse())); + + assertEquals("error", this.action.execute(context).getId()); + } +} diff --git a/cas-server-support-trusted/src/test/java/org/jasig/cas/adaptors/trusted/web/flow/PrincipalFromRequestUserPrincipalNonInteractiveCredentialsActionTests.java b/cas-server-support-trusted/src/test/java/org/jasig/cas/adaptors/trusted/web/flow/PrincipalFromRequestUserPrincipalNonInteractiveCredentialsActionTests.java new file mode 100644 index 000000000000..e39993104b59 --- /dev/null +++ b/cas-server-support-trusted/src/test/java/org/jasig/cas/adaptors/trusted/web/flow/PrincipalFromRequestUserPrincipalNonInteractiveCredentialsActionTests.java @@ -0,0 +1,106 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.trusted.web.flow; + +import org.jasig.cas.CentralAuthenticationServiceImpl; +import org.jasig.cas.adaptors.trusted.authentication.handler.support.PrincipalBearingCredentialsAuthenticationHandler; +import org.jasig.cas.adaptors.trusted.authentication.principal.PrincipalBearingPrincipalResolver; +import org.jasig.cas.authentication.AuthenticationHandler; +import org.jasig.cas.authentication.AuthenticationManager; +import org.jasig.cas.authentication.PolicyBasedAuthenticationManager; +import org.jasig.cas.authentication.principal.PrincipalResolver; +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.logout.LogoutManager; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.ticket.registry.DefaultTicketRegistry; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.test.MockRequestContext; + +import java.security.Principal; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Scott Battaglia + * @since 3.0.0.5 + */ +public class PrincipalFromRequestUserPrincipalNonInteractiveCredentialsActionTests { + + private PrincipalFromRequestUserPrincipalNonInteractiveCredentialsAction action; + + @Before + public void setUp() throws Exception { + this.action = new PrincipalFromRequestUserPrincipalNonInteractiveCredentialsAction(); + + final Map idGenerators = new HashMap<>(); + idGenerators.put(SimpleWebApplicationServiceImpl.class.getName(), new DefaultUniqueTicketIdGenerator()); + + + final AuthenticationManager authenticationManager = new PolicyBasedAuthenticationManager( + Collections.singletonMap( + new PrincipalBearingCredentialsAuthenticationHandler(), + new PrincipalBearingPrincipalResolver())); + + final CentralAuthenticationServiceImpl centralAuthenticationService = new CentralAuthenticationServiceImpl( + new DefaultTicketRegistry(), null, authenticationManager, new DefaultUniqueTicketIdGenerator(), + idGenerators, new NeverExpiresExpirationPolicy(), new NeverExpiresExpirationPolicy(), + mock(ServicesManager.class), mock(LogoutManager.class)); + + this.action.setCentralAuthenticationService(centralAuthenticationService); + } + + @Test + public void verifyRemoteUserExists() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setUserPrincipal(new Principal() { + @Override + public String getName() { + return "test"; + } + }); + + final MockRequestContext context = new MockRequestContext(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + + assertEquals("success", this.action.execute(context).getId()); + } + + @Test + public void verifyRemoteUserDoesntExists() throws Exception { + final MockRequestContext context = new MockRequestContext(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), new MockHttpServletRequest(), new MockHttpServletResponse())); + + assertEquals("error", this.action.execute(context).getId()); + } + +} diff --git a/cas-server-3.4.2/cas-server-support-spnego/.cvsignore b/cas-server-support-x509/.cvsignore similarity index 100% rename from cas-server-3.4.2/cas-server-support-spnego/.cvsignore rename to cas-server-support-x509/.cvsignore diff --git a/cas-server-support-x509/NOTICE b/cas-server-support-x509/NOTICE new file mode 100644 index 000000000000..f2e5c7dd135a --- /dev/null +++ b/cas-server-support-x509/NOTICE @@ -0,0 +1,109 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS Generic Support under Apache 2 + Apereo CAS Legacy Support under Apache 2 + Apereo CAS X.509 Client Certificate Support under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + Bouncy Castle Provider under Bouncy Castle Licence + CDI APIs under Apache License, Version 2.0 + Central Authentication Service under Jasig License + Commons CLI under The Apache Software License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + dom4j under BSD License + ehcache under The Apache Software License, Version 2.0 + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Metrics Core under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + Reflections under WTFPL or The New BSD License + servlet-api under Commons Development and Distribution License, Version 1.0 + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + VT Crypt Library under Apache 2 or GNU Lesser General Public License + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-support-x509/etc/testCA/intermediateCA/cacert.pem b/cas-server-support-x509/etc/testCA/intermediateCA/cacert.pem new file mode 100644 index 000000000000..8369b95cca19 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/intermediateCA/cacert.pem @@ -0,0 +1,84 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3237 (0xca5) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test Root CA + Validity + Not Before: Jan 19 16:47:44 2011 GMT + Not After : Dec 14 16:47:44 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test Intermediate CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:dd:46:76:c1:1d:68:29:5f:1e:90:93:66:88:75: + ca:21:91:e8:a6:cc:f3:b0:58:30:d2:94:2d:62:51: + 1d:63:e3:7b:7b:e7:f2:3a:bc:c4:c9:e5:48:b0:46: + cd:7b:c9:f7:27:bf:5f:e9:01:57:95:cb:c5:b1:e1: + 3c:85:b2:61:f3:0f:c9:fd:e0:8f:0a:fe:3a:f5:2a: + 27:09:22:44:dc:7c:c6:49:1d:f7:af:24:27:2c:dd: + 1d:77:81:dd:46:08:75:f5:3a:d4:7d:c7:92:b0:fb: + 36:02:04:6d:10:b5:43:e2:93:c4:60:19:40:ca:35: + f5:fb:60:e8:4c:d0:25:46:33:6c:b2:f2:8d:13:ef: + 7c:55:d9:58:78:4c:e6:7f:ef:7f:0a:30:44:76:3e: + 50:69:a4:cd:05:33:fa:dd:9a:1c:cb:60:46:46:58: + be:9a:cc:dd:94:63:09:d7:cb:ee:40:9c:a0:b0:37: + 42:3f:58:a8:9d:f2:07:d0:24:fc:96:5b:de:a1:e4: + ad:09:c3:c3:81:41:6b:41:95:07:e8:c7:6f:4a:01: + 77:e4:ac:2d:13:5a:f9:a5:44:5a:50:58:c4:83:7c: + e0:80:95:11:5c:42:ad:24:76:58:2b:e5:1d:1c:f9: + a5:69:34:23:07:6b:bf:3b:82:e7:62:5c:dc:9d:ae: + 93:db + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 12:38:AD:05:C8:59:B1:10:9F:FF:A8:6B:2E:CE:03:52:FF:59:D8:7F + X509v3 Authority Key Identifier: + keyid:F1:1A:2E:93:C7:42:81:1B:1B:2B:1B:1B:65:28:DD:AF:8D:EA:43:F2 + DirName:/C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=CAS Test Root CA + serial:0C:A5 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 62:1f:fa:01:79:8d:f8:f1:52:a6:4e:fa:50:4d:0b:c5:34:46: + ee:a4:17:51:7a:1f:96:4d:d5:2b:a9:40:0d:0f:fb:a0:1a:a9: + 39:88:95:36:14:8d:6c:3b:bb:e3:9f:5b:39:8c:fc:c0:49:0c: + e4:32:13:cf:b0:c9:1b:75:4c:b2:5b:c0:4e:0d:2b:d0:71:fc: + 01:02:eb:a9:61:37:75:1a:1f:69:61:53:b5:60:9f:84:73:dd: + 86:05:21:f3:03:38:81:0b:49:81:ba:1d:2e:86:4b:f7:40:0e: + 3f:4c:a1:2e:9f:cd:d0:1d:a0:9b:b8:42:fc:86:48:1f:a6:8a: + 28:50:d0:dc:ed:06:42:98:05:94:83:02:10:e7:e5:3e:89:ac: + 36:9d:ad:7b:a6:14:b2:fc:79:6f:1b:fe:7d:49:b4:46:0e:cb: + 66:ac:54:4b:45:88:44:f8:d9:ee:8d:d9:1d:a4:40:58:35:b6: + f9:8f:d5:ea:f8:72:67:4d:77:f5:f4:2f:47:1f:30:d1:73:8a: + 15:ef:e4:b0:b3:50:8e:21:ec:eb:75:65:4f:37:2b:d5:e1:21: + 33:76:7e:a6:87:bd:86:89:2a:c9:7a:f5:d7:0e:e0:db:a3:81: + 1b:3d:10:02:ce:13:88:47:d5:f5:f8:a4:c5:e1:bf:4b:c4:d7: + 0c:bc:11:a4 +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgICDKUwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgUm9vdCBD +QTAeFw0xMTAxMTkxNjQ3NDRaFw0zMjEyMTQxNjQ3NDRaMHcxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxITAfBgNVBAMTGENBUyBUZXN0IEludGVy +bWVkaWF0ZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN1GdsEd +aClfHpCTZoh1yiGR6KbM87BYMNKULWJRHWPje3vn8jq8xMnlSLBGzXvJ9ye/X+kB +V5XLxbHhPIWyYfMPyf3gjwr+OvUqJwkiRNx8xkkd968kJyzdHXeB3UYIdfU61H3H +krD7NgIEbRC1Q+KTxGAZQMo19ftg6EzQJUYzbLLyjRPvfFXZWHhM5n/vfwowRHY+ +UGmkzQUz+t2aHMtgRkZYvprM3ZRjCdfL7kCcoLA3Qj9YqJ3yB9Ak/JZb3qHkrQnD +w4FBa0GVB+jHb0oBd+SsLRNa+aVEWlBYxIN84ICVEVxCrSR2WCvlHRz5pWk0Iwdr +vzuC52Jc3J2uk9sCAwEAAaOBzTCByjAdBgNVHQ4EFgQUEjitBchZsRCf/6hrLs4D +Uv9Z2H8wgZoGA1UdIwSBkjCBj4AU8Rouk8dCgRsbKxsbZSjdr43qQ/Khc6RxMG8x +CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1p +bnN0ZXIxDjAMBgNVBAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxGTAXBgNVBAMTEENB +UyBUZXN0IFJvb3QgQ0GCAgylMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBAGIf+gF5jfjxUqZO+lBNC8U0Ru6kF1F6H5ZN1SupQA0P+6AaqTmIlTYUjWw7 +u+OfWzmM/MBJDOQyE8+wyRt1TLJbwE4NK9Bx/AEC66lhN3UaH2lhU7Vgn4Rz3YYF +IfMDOIELSYG6HS6GS/dADj9MoS6fzdAdoJu4QvyGSB+miihQ0NztBkKYBZSDAhDn +5T6JrDadrXumFLL8eW8b/n1JtEYOy2asVEtFiET42e6N2R2kQFg1tvmP1er4cmdN +d/X0L0cfMNFzihXv5LCzUI4h7Ot1ZU83K9XhITN2fqaHvYaJKsl69dcO4NujgRs9 +EALOE4hH1fX4pMXhv0vE1wy8EaQ= +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/etc/testCA/intermediateCA/crlnumber b/cas-server-support-x509/etc/testCA/intermediateCA/crlnumber new file mode 100644 index 000000000000..2c7456e3eb66 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/intermediateCA/crlnumber @@ -0,0 +1 @@ +07 diff --git a/cas-server-support-x509/etc/testCA/intermediateCA/crlnumber.old b/cas-server-support-x509/etc/testCA/intermediateCA/crlnumber.old new file mode 100644 index 000000000000..cd672a533b7f --- /dev/null +++ b/cas-server-support-x509/etc/testCA/intermediateCA/crlnumber.old @@ -0,0 +1 @@ +06 diff --git a/cas-server-support-x509/etc/testCA/intermediateCA/index.txt b/cas-server-support-x509/etc/testCA/intermediateCA/index.txt new file mode 100644 index 000000000000..93c537964e7f --- /dev/null +++ b/cas-server-support-x509/etc/testCA/intermediateCA/index.txt @@ -0,0 +1 @@ +V 321214182539Z 0CA5 unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=CAS Test User CA diff --git a/cas-server-support-x509/etc/testCA/intermediateCA/index.txt.attr b/cas-server-support-x509/etc/testCA/intermediateCA/index.txt.attr new file mode 100644 index 000000000000..8f7e63a3475c --- /dev/null +++ b/cas-server-support-x509/etc/testCA/intermediateCA/index.txt.attr @@ -0,0 +1 @@ +unique_subject = yes diff --git a/cas-server-support-x509/etc/testCA/intermediateCA/index.txt.old b/cas-server-support-x509/etc/testCA/intermediateCA/index.txt.old new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cas-server-support-x509/etc/testCA/intermediateCA/newcerts/0CA5.pem b/cas-server-support-x509/etc/testCA/intermediateCA/newcerts/0CA5.pem new file mode 100644 index 000000000000..f431a7723e68 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/intermediateCA/newcerts/0CA5.pem @@ -0,0 +1,84 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3237 (0xca5) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test Intermediate CA + Validity + Not Before: Jan 19 18:25:39 2011 GMT + Not After : Dec 14 18:25:39 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:ab:9d:39:88:09:d6:e8:41:65:21:b8:8c:a2:b7: + d6:6c:a9:cf:f5:fe:f9:c7:ec:e3:b1:b2:16:07:8d: + 2a:8d:d8:bc:7b:1c:73:85:09:82:c9:a2:9a:af:0a: + b3:4e:dc:53:bd:9f:27:d1:6d:91:e4:77:8d:fe:bc: + 74:36:26:54:08:81:e6:1f:74:d6:5a:a4:41:7d:95: + 93:5c:5b:17:1e:a3:3b:90:08:96:2d:6a:e8:00:c1: + b1:3c:0a:0b:93:2d:c4:00:27:d5:ad:ed:d1:b2:66: + 44:2c:60:b0:2f:92:26:7b:d4:8b:24:01:d6:62:e2: + 48:17:2e:3c:0d:15:fc:df:42:59:90:30:84:0b:81: + 86:2a:01:8b:58:ba:15:03:ad:6e:07:58:cf:a1:f7: + 2e:6e:c8:5c:f2:b1:ae:90:36:67:d4:39:96:b8:04: + b4:66:b0:f7:46:2a:ba:21:43:46:b9:fb:93:d1:39: + d6:fd:40:b4:d2:9d:e9:d8:4f:dc:ae:14:8e:73:05: + 26:8a:5c:21:5c:39:a5:c0:9a:66:1c:b8:9e:94:51: + ee:71:8c:99:a9:c2:2e:39:00:85:04:d1:14:a7:23: + 00:4f:7b:52:35:61:0a:d2:0a:64:c9:32:13:7d:46: + 7d:35:f6:b1:31:48:38:60:6a:9b:b5:bf:3d:23:35: + 91:21 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + X509v3 Authority Key Identifier: + keyid:12:38:AD:05:C8:59:B1:10:9F:FF:A8:6B:2E:CE:03:52:FF:59:D8:7F + DirName:/C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=CAS Test Root CA + serial:0C:A5 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 05:95:6a:d7:a8:b5:cc:94:a2:54:1a:27:04:44:55:f2:cd:2f: + d3:15:ee:5c:80:99:63:14:b4:23:86:fe:6c:90:de:3a:ac:26: + 68:9c:ef:ab:55:56:00:77:4e:dc:93:32:32:eb:78:da:2a:fb: + 86:2f:7a:d0:e3:d8:b3:ec:02:57:60:41:fb:4d:b2:58:45:26: + 9a:ba:35:96:6b:ff:0c:6e:68:d4:ca:69:74:cc:55:e5:ef:74: + 07:bf:8a:c0:7a:63:22:fd:06:1d:93:18:0d:c2:8f:12:45:3a: + 99:0f:7f:20:23:27:3a:8b:75:2c:26:59:37:d6:75:76:4b:4b: + 87:34:a8:99:f9:eb:90:bf:5a:e5:43:a0:fa:d6:42:cf:1e:c4: + 58:43:c2:44:3a:90:84:5c:82:83:db:6b:c4:21:e7:5e:39:ea: + ce:4f:be:28:e1:bf:48:e3:d6:fb:0a:88:92:bd:b3:df:2c:0c: + 66:d2:23:5d:03:89:5f:de:4f:ae:8f:05:47:9a:aa:84:70:b5: + 74:bd:19:2c:cd:ca:0b:f5:9a:82:5f:1b:e2:a7:c9:0f:9a:bc: + c2:69:f4:5c:f2:29:cb:de:c2:f6:63:8d:df:15:b6:34:64:0d: + aa:a3:42:32:e6:5b:eb:57:10:e3:01:b1:78:b6:fc:a2:ff:62: + 3f:ba:43:4a +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgICDKUwDQYJKoZIhvcNAQEFBQAwdzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEhMB8GA1UEAxMYQ0FTIFRlc3QgSW50ZXJt +ZWRpYXRlIENBMB4XDTExMDExOTE4MjUzOVoXDTMyMTIxNDE4MjUzOVowbzELMAkG +A1UEBhMCVVMxETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3Rl +cjEOMAwGA1UEChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRl +c3QgVXNlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKudOYgJ +1uhBZSG4jKK31mypz/X++cfs47GyFgeNKo3YvHscc4UJgsmimq8Ks07cU72fJ9Ft +keR3jf68dDYmVAiB5h901lqkQX2Vk1xbFx6jO5AIli1q6ADBsTwKC5MtxAAn1a3t +0bJmRCxgsC+SJnvUiyQB1mLiSBcuPA0V/N9CWZAwhAuBhioBi1i6FQOtbgdYz6H3 +Lm7IXPKxrpA2Z9Q5lrgEtGaw90YquiFDRrn7k9E51v1AtNKd6dhP3K4UjnMFJopc +IVw5pcCaZhy4npRR7nGMmanCLjkAhQTRFKcjAE97UjVhCtIKZMkyE31GfTX2sTFI +OGBqm7W/PSM1kSECAwEAAaOBzTCByjAdBgNVHQ4EFgQUGRt4jZNisklmkFVuULPQ +r2HrOfMwgZoGA1UdIwSBkjCBj4AUEjitBchZsRCf/6hrLs4DUv9Z2H+hc6RxMG8x +CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1p +bnN0ZXIxDjAMBgNVBAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxGTAXBgNVBAMTEENB +UyBUZXN0IFJvb3QgQ0GCAgylMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBAAWVateotcyUolQaJwREVfLNL9MV7lyAmWMUtCOG/myQ3jqsJmic76tVVgB3 +TtyTMjLreNoq+4YvetDj2LPsAldgQftNslhFJpq6NZZr/wxuaNTKaXTMVeXvdAe/ +isB6YyL9Bh2TGA3CjxJFOpkPfyAjJzqLdSwmWTfWdXZLS4c0qJn565C/WuVDoPrW +Qs8exFhDwkQ6kIRcgoPba8Qh51456s5Pvijhv0jj1vsKiJK9s98sDGbSI10DiV/e +T66PBUeaqoRwtXS9GSzNygv1moJfG+KnyQ+avMJp9FzyKcvewvZjjd8VtjRkDaqj +QjLmW+tXEOMBsXi2/KL/Yj+6Q0o= +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/etc/testCA/intermediateCA/private/cakey.pem b/cas-server-support-x509/etc/testCA/intermediateCA/private/cakey.pem new file mode 100644 index 000000000000..eac6cbe1276d --- /dev/null +++ b/cas-server-support-x509/etc/testCA/intermediateCA/private/cakey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA3UZ2wR1oKV8ekJNmiHXKIZHopszzsFgw0pQtYlEdY+N7e+fy +OrzEyeVIsEbNe8n3J79f6QFXlcvFseE8hbJh8w/J/eCPCv469SonCSJE3HzGSR33 +ryQnLN0dd4HdRgh19TrUfceSsPs2AgRtELVD4pPEYBlAyjX1+2DoTNAlRjNssvKN +E+98VdlYeEzmf+9/CjBEdj5QaaTNBTP63Zocy2BGRli+mszdlGMJ18vuQJygsDdC +P1ionfIH0CT8llveoeStCcPDgUFrQZUH6MdvSgF35KwtE1r5pURaUFjEg3zggJUR +XEKtJHZYK+UdHPmlaTQjB2u/O4LnYlzcna6T2wIDAQABAoIBABONaWrxDUYymIHP +9Ix0VBMZUISEsbfQYygIXeZFIqKz9DJjsXzNN1pbv0uMRhX9J7DEcVYmfzXGVsa8 +D1wFAjC3IXMkh59CDbWiLubYt1sAT4kgc8VvHWMSessXl5TbpIix1Rr0Kpsm54PQ +Hk8OpVv9EKopMRdtIzo+Ouimr9R/BrHhRc3Dm80vl5Zvke7yiZ0k4/90dcUe1Ly9 +IxCq7k7aNov8V0dc1xaFkYCovTv7VZNcvZCamPnATSZOaxT5IR4yxX6TjD2ouRqj +d4bRz7fw7hSsnhdjLjb5YTYzmUluBSrmCXpPy4daI+dMGdbaYgLU6iYKPmCEYhku +H+POF1kCgYEA9tAJNt+F0xvj68OlKZHXqp4eD7cTaIBrkhmABF7KG3HDl9E76jb4 +VFDnQJgMzNHnwlK1ort/hOK1KAbYDHXBEE20vQsldbQwCY/Wo2nuMnVkuB/NuBjl +wPPWA4LyDwitQ7n66Lqu3EJG9fDA41UHtwV0uCr93DFJDtbn9uEN4/cCgYEA5YMS +umAGvvwvTQ11jBppUmFwcrWCpVrDeGMFnvmeUXY8HeSx4eLUj3sQ+2nSDkJXnMa0 +MtcU+Dd9dtt9D85fCONVN5gDXjyaQWhV7NwnU6brFQRGQ6TGDG9+KCOGHj945aG5 +kqrN9F/3oMOQChWP18pmCF70Kvl7dyIDox7mTj0CgYEAhFdWqYK/Wkx5MSEN/bVD +BCyWh+ytznoVtBhSSYWeL4sXyt8ZlmP1UhizdzJQor7m247H8ENDhDif1J+e10eI +xzozcK56XpvIqkfjWmphm9LQt93tzAoloAnfNrDPJHqNlWQzAvme62HqdNOsSfl9 +EoktZdc/mgMfXoKjjQBuF08CgYEAgatbUmdciFzs0IjRJ9XoOX4JzRVV+dxc4dB4 +ZYg8dWPqtSz3f1rroyDxyEnSPvImk9IED1FLbK3VOejqMJr1QI1IIT/Gb2Cn+lpb +aZY8tdtxqA8hL6iRqIb5bO4dIUvVH1APC5lrAS2TN4YmBDyIZ2/X09Tp+Lm5kuZp +zahwkDUCgYEA7R8t8JrnKX6GfUeqTLHHmQU6M5E8fzxeYXRouUfvrLYoRnKSdp13 +vw+3CfrspnuaW3GF3phnyDHvHFzsGa2dfY0ImXaIIag4TZfOofvGNQKOCo+W6Cxw +ExXtA0VbNNqIStxmO9yh7Uppeq30le01GomUzi/uRcsso9QdrFXsvz0= +-----END RSA PRIVATE KEY----- diff --git a/cas-server-support-x509/etc/testCA/intermediateCA/serial b/cas-server-support-x509/etc/testCA/intermediateCA/serial new file mode 100644 index 000000000000..7671dd80b51a --- /dev/null +++ b/cas-server-support-x509/etc/testCA/intermediateCA/serial @@ -0,0 +1 @@ +0CA6 diff --git a/cas-server-support-x509/etc/testCA/intermediateCA/serial.old b/cas-server-support-x509/etc/testCA/intermediateCA/serial.old new file mode 100644 index 000000000000..cc19656d79ba --- /dev/null +++ b/cas-server-support-x509/etc/testCA/intermediateCA/serial.old @@ -0,0 +1 @@ +0CA5 diff --git a/cas-server-support-x509/etc/testCA/openssl.cnf b/cas-server-support-x509/etc/testCA/openssl.cnf new file mode 100644 index 000000000000..8c4580da8bff --- /dev/null +++ b/cas-server-support-x509/etc/testCA/openssl.cnf @@ -0,0 +1,285 @@ +# +# OpenSSL configuration file for CAS Test CA chain. + +# This definition stops the following lines choking if HOME isn't +# defined. +HOME = . +RANDFILE = $ENV::HOME/.rnd + +# Extra OBJECT IDENTIFIER info: +oid_section = new_oids + +[ new_oids ] +# We can add new OIDs in here for use by 'ca' and 'req'. +# Add a simple OID like this: +# testoid1=1.2.3.4 + +#################################################################### +[ ca ] +default_ca = userCA # The default ca section + +#################################################################### + +#---------------------------- +# CAS Test Root CA +#---------------------------- +[ rootCA ] + +dir = ./rootCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem # The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = usr_cert # The extentions to add to the cert + +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +default_days = 8000 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = sha1 # which md to use. +preserve = no # keep passed DN ordering + +policy = policy_match + +#---------------------------- +# CAS Test Intermediate CA +#---------------------------- +[ intermediateCA ] + +dir = ./intermediateCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem # The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = usr_cert # The extentions to add to the cert + +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +default_days = 8000 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = sha1 # which md to use. +preserve = no # keep passed DN ordering + +policy = policy_match + + +#---------------------------- +# CAS Test User CA +#---------------------------- +[ userCA ] + +dir = ./userCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem # The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = usr_cert # The extentions to add to the cert + +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +default_days = 8000 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = sha1 # which md to use. +preserve = no # keep passed DN ordering + +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = match +localityName = match +organizationName = match +organizationalUnitName = match +commonName = supplied + +# For the 'anything' policy +[ policy_anything ] +countryName = match +stateOrProvinceName = match +localityName = match +organizationName = match +organizationalUnitName = match +commonName = supplied + +#################################################################### +[ req ] +default_bits = 2048 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extentions to add to the self signed cert + +# This sets a mask for permitted string types. There are several options. +# default: PrintableString, T61String, BMPString. +# pkix : PrintableString, BMPString. +# utf8only: only UTF8Strings. +# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). +# MASK:XXXX a literal mask value. +# WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings +# so use this option with caution! +string_mask = nombstr + +# req_extensions = v3_req # The extensions to add to a certificate request + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = US +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Colorado + +localityName = Locality Name (eg, city) +localityName_default = Westminster + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Jasig + +# we can do this but it is not needed normally :-) +#1.organizationName = Second Organization Name (eg, company) +#1.organizationName_default = World Wide Web Pty Ltd + +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = CAS + +commonName = Common Name (eg, YOUR name) +commonName_max = 64 + +[ req_attributes ] +challengePassword = A challenge password +challengePassword_min = 4 +challengePassword_max = 20 + +[ usr_cert ] + +# These extensions are added when 'ca' signs a request. + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# This is typical in keyUsage for a client certificate. +#keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +crlDistributionPoints=URI:http://localhost:8085/ca.crl,URI:http://example.com/ca.crl + +# Use following to produce CRL with querystring that needs to be encoded +# i.e. the EJBCA use case +#crlDistributionPoints = URI:http://localhost:8085/ca?issuer=CN=CAS Test User CA + +# Use following to produce a list with just a single entry +#crlDistributionPoints=URI:http://localhost:8085/ca.crl + +[ v3_req ] +# Extensions to add to a certificate request +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[ v3_ca ] +# Extensions for a typical CA + +# PKIX recommendation. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer:always + +# This is what PKIX recommends but some broken software chokes on critical +# extensions. +#basicConstraints = critical,CA:true +# So we do this instead. +basicConstraints = CA:true + +# Key usage: this is typical for a CA certificate. However since it will +# prevent it being used as an test self-signed certificate it is best +# left out by default. +# keyUsage = cRLSign, keyCertSign + +# Include email address in subject alt name: another PKIX recommendation +# subjectAltName=email:copy +# Copy issuer details +# issuerAltName=issuer:copy + +[ crl_ext ] + +# CRL extensions. +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always,issuer:always + +[ proxy_cert_ext ] +# These extensions should be added when creating a proxy certificate + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer:always + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +# This really needs to be in place for it to be a proxy certificate. +proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo diff --git a/cas-server-support-x509/etc/testCA/readme.txt b/cas-server-support-x509/etc/testCA/readme.txt new file mode 100644 index 000000000000..78f24449f850 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/readme.txt @@ -0,0 +1,27 @@ +# Some sample commands to create certificates with openssl ca command + +# Create the intermediate CA certificate +# NOTE: The -extensions v3_ca is the key to creating a CA cert +openssl ca -config openssl.cnf -name rootCA \ + -extensions v3_ca \ + -in cas-test-intermediate-ca.csr \ + -key intermediateCA/private/cakey.pem \ + -out intermediateCA/cacert.pem + +# Create a new certificate issued by CAS Test User CA +openssl req -config openssl.cnf -new \ + -out user-valid.csr -key userCA/private/cert.key +openssl ca -config openssl.cnf -name userCA \ + -key userCA/private/cakey.key \ + -in user-valid.csr \ + -out user-valid.crt + +# Revoke a certificate issued by CAS Test User CA +openssl ca -config openssl.cnf \ + -revoke userCA/newcerts/0CA7.pem \ + -crl_reason keyCompromise + +# Generate a CRL for CAS Test User CA +openssl ca -config openssl.cnf -name userCA \ + -gencrl \ + -out userCA/crl/crl-`cat userCA/crlnumber`.pem diff --git a/cas-server-support-x509/etc/testCA/rootCA/cacert.pem b/cas-server-support-x509/etc/testCA/rootCA/cacert.pem new file mode 100644 index 000000000000..91ca4a91ff33 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/rootCA/cacert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEKDCCAxCgAwIBAgICDKUwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgUm9vdCBD +QTAeFw0xMDEyMDMxODUyMDBaFw0zODA0MjAxODUyMDBaMG8xCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxGTAXBgNVBAMTEENBUyBUZXN0IFJvb3Qg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCe6snxpRVgR8OgTwve +Lb326AeEpplLEDeyR9IuawwfIovCFcWhyuH5CJq3KxlwEKbYXr/Dd1xRcigZDMJX +dqJ/Ke4Gsq2fNyur1pk0LUOTGJC7sVeSdbfXEVR3hDns27q/avinvYb1rwfr6Q5e +mJWrqE4J30Ff0AZ1gGXUF8hqYsxocMRQ1hezdSH4B+ukJY48FOxfEoxcTsqSNSHg +yhbVuZ7pJXh/JcCCloNPLvEcDsxwKcXNGdMrO6PIF7DuIFIMMERyhzDY/ZuKBZJv +bhY0Lz2RyMZh2rA6kw7YQLpTzxqakz+1WSixKY++fmeG6VXK/9y4xi4YpfQ6rq2l +Y5vvAgMBAAGjgc0wgcowHQYDVR0OBBYEFPEaLpPHQoEbGysbG2Uo3a+N6kPyMIGa +BgNVHSMEgZIwgY+AFPEaLpPHQoEbGysbG2Uo3a+N6kPyoXOkcTBvMQswCQYDVQQG +EwJVUzERMA8GA1UECBMIQ29sb3JhZG8xFDASBgNVBAcTC1dlc3RtaW5zdGVyMQ4w +DAYDVQQKEwVKYXNpZzEMMAoGA1UECxMDQ0FTMRkwFwYDVQQDExBDQVMgVGVzdCBS +b290IENBggIMpTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAmDP2G +r/Vgowgt0ncwkDc5hiyd22cavavZ2n8+3mBaun2vc7ytbdhOMWSVrO5RzWj+IdGx +2MuTmtS3uoAMxyKLYlSD1DyOZpBeFj0+YCMeNr42bE7zhQyogMockbMAxgC0oAtL +mL1G+zVEnva76HLzkes2faP65u36LYwiZc5Cld3hHI8R1F18Vj9us+XAHMZlK7fS +c6d7FpR8SOUP0LyRYDaD26DuU3TjoufIpQFuMpzY5Z5LK5zseVnR8n/Ue4UzBJCh +1AAhMOyhfBB8M5Ypkn+csHcdd/Jd/hMy0N8BniGHRFug0RMlgY34YNK1sLo59qUV +y9ThiKbmq1N+znoL +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/etc/testCA/rootCA/crlnumber b/cas-server-support-x509/etc/testCA/rootCA/crlnumber new file mode 100644 index 000000000000..9e22bcb8e344 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/rootCA/crlnumber @@ -0,0 +1 @@ +02 diff --git a/cas-server-support-x509/etc/testCA/rootCA/crlnumber.old b/cas-server-support-x509/etc/testCA/rootCA/crlnumber.old new file mode 100644 index 000000000000..8a0f05e166aa --- /dev/null +++ b/cas-server-support-x509/etc/testCA/rootCA/crlnumber.old @@ -0,0 +1 @@ +01 diff --git a/cas-server-support-x509/etc/testCA/rootCA/index.txt b/cas-server-support-x509/etc/testCA/rootCA/index.txt new file mode 100644 index 000000000000..c8ccc6fc7ad5 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/rootCA/index.txt @@ -0,0 +1 @@ +V 321214164744Z 0CA5 unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=CAS Test Intermediate CA diff --git a/cas-server-support-x509/etc/testCA/rootCA/index.txt.attr b/cas-server-support-x509/etc/testCA/rootCA/index.txt.attr new file mode 100644 index 000000000000..8f7e63a3475c --- /dev/null +++ b/cas-server-support-x509/etc/testCA/rootCA/index.txt.attr @@ -0,0 +1 @@ +unique_subject = yes diff --git a/cas-server-support-x509/etc/testCA/rootCA/index.txt.old b/cas-server-support-x509/etc/testCA/rootCA/index.txt.old new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cas-server-support-x509/etc/testCA/rootCA/newcerts/0CA5.pem b/cas-server-support-x509/etc/testCA/rootCA/newcerts/0CA5.pem new file mode 100644 index 000000000000..8369b95cca19 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/rootCA/newcerts/0CA5.pem @@ -0,0 +1,84 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3237 (0xca5) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test Root CA + Validity + Not Before: Jan 19 16:47:44 2011 GMT + Not After : Dec 14 16:47:44 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test Intermediate CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:dd:46:76:c1:1d:68:29:5f:1e:90:93:66:88:75: + ca:21:91:e8:a6:cc:f3:b0:58:30:d2:94:2d:62:51: + 1d:63:e3:7b:7b:e7:f2:3a:bc:c4:c9:e5:48:b0:46: + cd:7b:c9:f7:27:bf:5f:e9:01:57:95:cb:c5:b1:e1: + 3c:85:b2:61:f3:0f:c9:fd:e0:8f:0a:fe:3a:f5:2a: + 27:09:22:44:dc:7c:c6:49:1d:f7:af:24:27:2c:dd: + 1d:77:81:dd:46:08:75:f5:3a:d4:7d:c7:92:b0:fb: + 36:02:04:6d:10:b5:43:e2:93:c4:60:19:40:ca:35: + f5:fb:60:e8:4c:d0:25:46:33:6c:b2:f2:8d:13:ef: + 7c:55:d9:58:78:4c:e6:7f:ef:7f:0a:30:44:76:3e: + 50:69:a4:cd:05:33:fa:dd:9a:1c:cb:60:46:46:58: + be:9a:cc:dd:94:63:09:d7:cb:ee:40:9c:a0:b0:37: + 42:3f:58:a8:9d:f2:07:d0:24:fc:96:5b:de:a1:e4: + ad:09:c3:c3:81:41:6b:41:95:07:e8:c7:6f:4a:01: + 77:e4:ac:2d:13:5a:f9:a5:44:5a:50:58:c4:83:7c: + e0:80:95:11:5c:42:ad:24:76:58:2b:e5:1d:1c:f9: + a5:69:34:23:07:6b:bf:3b:82:e7:62:5c:dc:9d:ae: + 93:db + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 12:38:AD:05:C8:59:B1:10:9F:FF:A8:6B:2E:CE:03:52:FF:59:D8:7F + X509v3 Authority Key Identifier: + keyid:F1:1A:2E:93:C7:42:81:1B:1B:2B:1B:1B:65:28:DD:AF:8D:EA:43:F2 + DirName:/C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=CAS Test Root CA + serial:0C:A5 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 62:1f:fa:01:79:8d:f8:f1:52:a6:4e:fa:50:4d:0b:c5:34:46: + ee:a4:17:51:7a:1f:96:4d:d5:2b:a9:40:0d:0f:fb:a0:1a:a9: + 39:88:95:36:14:8d:6c:3b:bb:e3:9f:5b:39:8c:fc:c0:49:0c: + e4:32:13:cf:b0:c9:1b:75:4c:b2:5b:c0:4e:0d:2b:d0:71:fc: + 01:02:eb:a9:61:37:75:1a:1f:69:61:53:b5:60:9f:84:73:dd: + 86:05:21:f3:03:38:81:0b:49:81:ba:1d:2e:86:4b:f7:40:0e: + 3f:4c:a1:2e:9f:cd:d0:1d:a0:9b:b8:42:fc:86:48:1f:a6:8a: + 28:50:d0:dc:ed:06:42:98:05:94:83:02:10:e7:e5:3e:89:ac: + 36:9d:ad:7b:a6:14:b2:fc:79:6f:1b:fe:7d:49:b4:46:0e:cb: + 66:ac:54:4b:45:88:44:f8:d9:ee:8d:d9:1d:a4:40:58:35:b6: + f9:8f:d5:ea:f8:72:67:4d:77:f5:f4:2f:47:1f:30:d1:73:8a: + 15:ef:e4:b0:b3:50:8e:21:ec:eb:75:65:4f:37:2b:d5:e1:21: + 33:76:7e:a6:87:bd:86:89:2a:c9:7a:f5:d7:0e:e0:db:a3:81: + 1b:3d:10:02:ce:13:88:47:d5:f5:f8:a4:c5:e1:bf:4b:c4:d7: + 0c:bc:11:a4 +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgICDKUwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgUm9vdCBD +QTAeFw0xMTAxMTkxNjQ3NDRaFw0zMjEyMTQxNjQ3NDRaMHcxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxITAfBgNVBAMTGENBUyBUZXN0IEludGVy +bWVkaWF0ZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN1GdsEd +aClfHpCTZoh1yiGR6KbM87BYMNKULWJRHWPje3vn8jq8xMnlSLBGzXvJ9ye/X+kB +V5XLxbHhPIWyYfMPyf3gjwr+OvUqJwkiRNx8xkkd968kJyzdHXeB3UYIdfU61H3H +krD7NgIEbRC1Q+KTxGAZQMo19ftg6EzQJUYzbLLyjRPvfFXZWHhM5n/vfwowRHY+ +UGmkzQUz+t2aHMtgRkZYvprM3ZRjCdfL7kCcoLA3Qj9YqJ3yB9Ak/JZb3qHkrQnD +w4FBa0GVB+jHb0oBd+SsLRNa+aVEWlBYxIN84ICVEVxCrSR2WCvlHRz5pWk0Iwdr +vzuC52Jc3J2uk9sCAwEAAaOBzTCByjAdBgNVHQ4EFgQUEjitBchZsRCf/6hrLs4D +Uv9Z2H8wgZoGA1UdIwSBkjCBj4AU8Rouk8dCgRsbKxsbZSjdr43qQ/Khc6RxMG8x +CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1p +bnN0ZXIxDjAMBgNVBAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxGTAXBgNVBAMTEENB +UyBUZXN0IFJvb3QgQ0GCAgylMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBAGIf+gF5jfjxUqZO+lBNC8U0Ru6kF1F6H5ZN1SupQA0P+6AaqTmIlTYUjWw7 +u+OfWzmM/MBJDOQyE8+wyRt1TLJbwE4NK9Bx/AEC66lhN3UaH2lhU7Vgn4Rz3YYF +IfMDOIELSYG6HS6GS/dADj9MoS6fzdAdoJu4QvyGSB+miihQ0NztBkKYBZSDAhDn +5T6JrDadrXumFLL8eW8b/n1JtEYOy2asVEtFiET42e6N2R2kQFg1tvmP1er4cmdN +d/X0L0cfMNFzihXv5LCzUI4h7Ot1ZU83K9XhITN2fqaHvYaJKsl69dcO4NujgRs9 +EALOE4hH1fX4pMXhv0vE1wy8EaQ= +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/etc/testCA/rootCA/private/cakey.pem b/cas-server-support-x509/etc/testCA/rootCA/private/cakey.pem new file mode 100644 index 000000000000..49ee61ccfa69 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/rootCA/private/cakey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAnurJ8aUVYEfDoE8L3i299ugHhKaZSxA3skfSLmsMHyKLwhXF +ocrh+QiatysZcBCm2F6/w3dcUXIoGQzCV3aifynuBrKtnzcrq9aZNC1DkxiQu7FX +knW31xFUd4Q57Nu6v2r4p72G9a8H6+kOXpiVq6hOCd9BX9AGdYBl1BfIamLMaHDE +UNYXs3Uh+AfrpCWOPBTsXxKMXE7KkjUh4MoW1bme6SV4fyXAgpaDTy7xHA7McCnF +zRnTKzujyBew7iBSDDBEcocw2P2bigWSb24WNC89kcjGYdqwOpMO2EC6U88ampM/ +tVkosSmPvn5nhulVyv/cuMYuGKX0Oq6tpWOb7wIDAQABAoIBAQCPWiBcuJv1xAa1 +31hHTV/lEB1oDZiOW8zCJ4Nzl+DRXSpRI66pagg5ywc1X82Mh5jZ6W52dn9ygNWm +8xmEKGIg78PlityPmQbt9aExrfsc/7XfefQTC9mQSfO19DMEdJma/nXbal4J1aAB +sLPjpSSuOh5bTWb9dPu+ltpEXkfNnnHkwi2BQOnTwe7TgFoC6xXTcvQ6tyGdgBdk +KsFvwYepomnWEuUNmFTyM/YzbcdZunGAYaZGxAbvY+eRn/lqBdr9wKRzvCGG/VkR +M4lVqeNQ14fUdF0Qjo5pe5JCdWLKfFB0yrcAFK5rvcmK8Tx0NcKGaPM4G0X/tHpO +147xE2TRAoGBAM5ON8sbiO7S+uN1k0G6ee8sRYXjyi2/qnCwZgS87p+kueE6ZLPL +gBbg06kRbwjWgu7XH1qKQvCVlUfANcW0JoZJYzzpJPQONMyHrX2Kk92NmNtz8Rji +6zFo0TYFi/8cjumza5YXmpykOTMx0V7JScl18DgUKULk3ey1fn0xrCS7AoGBAMUy +YHTKFuebeqy3/9enHgQ949uwALf4PP25GD770J/+zTMgpV6vloNHvQxR16j7V8Q6 +KsFG3YMEUhHoB9bdhJQQ53gxVBScTIczjR35SUf6FvcHG4KNaLtHds/v5w2LYlX7 +qIQ7wCE6cx9/Wjt6J+gm1TYcfdFjqfxMjFbnDIxdAoGAJ3ctEyykSFOR7Rtb3TII +C2njrtZBofjNaRtFmNhvqZiAeoaKJx7h1P6TXv9Xx1AJ4hjDBbX3UkC7OuuZHcX1 +rLCzTD6pdAqXrRyzYpW07agdurV2RLV8GoqbowLyog3bhPduuFqxmA+OZa29CK3m +8KQgHQo2TnarwZGCrf5nmOsCgYEAlYXuR4AYwANPkHlwP/fStcqr5J9e3CAZNxXL +Qa608SxGMbdWTSZ8zQxvV1ETYmk6kmq8kpMGUVrMVBGJKC4NYiwzJxrYM0yUZov7 +O7gTDcWe9i5oeKR6vk6g02iH3OhMZwAMNkAMHeXw9vgc1i545fnT/1S5PjUA5nKw +kxBcnm0CgYEAshNoax8i+Lk+l32JuMHSxhqWUbaQT7LfLLS0AHWD3l5txnnWjt8J +jljWOT9Vtci0r8OCxCf1ezsEL0pmiRQj1XudCX2tDI3WaSmjVV4OrLYo5CxvjSva +4qadoFeYP5sCvfo3n4S6XUTSvc6vueNizWgq668WilMNNSxCc3zawKs= +-----END RSA PRIVATE KEY----- diff --git a/cas-server-support-x509/etc/testCA/rootCA/serial b/cas-server-support-x509/etc/testCA/rootCA/serial new file mode 100644 index 000000000000..7671dd80b51a --- /dev/null +++ b/cas-server-support-x509/etc/testCA/rootCA/serial @@ -0,0 +1 @@ +0CA6 diff --git a/cas-server-support-x509/etc/testCA/rootCA/serial.old b/cas-server-support-x509/etc/testCA/rootCA/serial.old new file mode 100644 index 000000000000..cc19656d79ba --- /dev/null +++ b/cas-server-support-x509/etc/testCA/rootCA/serial.old @@ -0,0 +1 @@ +0CA5 diff --git a/cas-server-support-x509/etc/testCA/userCA/cacert.pem b/cas-server-support-x509/etc/testCA/userCA/cacert.pem new file mode 100644 index 000000000000..f431a7723e68 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/cacert.pem @@ -0,0 +1,84 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3237 (0xca5) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test Intermediate CA + Validity + Not Before: Jan 19 18:25:39 2011 GMT + Not After : Dec 14 18:25:39 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:ab:9d:39:88:09:d6:e8:41:65:21:b8:8c:a2:b7: + d6:6c:a9:cf:f5:fe:f9:c7:ec:e3:b1:b2:16:07:8d: + 2a:8d:d8:bc:7b:1c:73:85:09:82:c9:a2:9a:af:0a: + b3:4e:dc:53:bd:9f:27:d1:6d:91:e4:77:8d:fe:bc: + 74:36:26:54:08:81:e6:1f:74:d6:5a:a4:41:7d:95: + 93:5c:5b:17:1e:a3:3b:90:08:96:2d:6a:e8:00:c1: + b1:3c:0a:0b:93:2d:c4:00:27:d5:ad:ed:d1:b2:66: + 44:2c:60:b0:2f:92:26:7b:d4:8b:24:01:d6:62:e2: + 48:17:2e:3c:0d:15:fc:df:42:59:90:30:84:0b:81: + 86:2a:01:8b:58:ba:15:03:ad:6e:07:58:cf:a1:f7: + 2e:6e:c8:5c:f2:b1:ae:90:36:67:d4:39:96:b8:04: + b4:66:b0:f7:46:2a:ba:21:43:46:b9:fb:93:d1:39: + d6:fd:40:b4:d2:9d:e9:d8:4f:dc:ae:14:8e:73:05: + 26:8a:5c:21:5c:39:a5:c0:9a:66:1c:b8:9e:94:51: + ee:71:8c:99:a9:c2:2e:39:00:85:04:d1:14:a7:23: + 00:4f:7b:52:35:61:0a:d2:0a:64:c9:32:13:7d:46: + 7d:35:f6:b1:31:48:38:60:6a:9b:b5:bf:3d:23:35: + 91:21 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + X509v3 Authority Key Identifier: + keyid:12:38:AD:05:C8:59:B1:10:9F:FF:A8:6B:2E:CE:03:52:FF:59:D8:7F + DirName:/C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=CAS Test Root CA + serial:0C:A5 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 05:95:6a:d7:a8:b5:cc:94:a2:54:1a:27:04:44:55:f2:cd:2f: + d3:15:ee:5c:80:99:63:14:b4:23:86:fe:6c:90:de:3a:ac:26: + 68:9c:ef:ab:55:56:00:77:4e:dc:93:32:32:eb:78:da:2a:fb: + 86:2f:7a:d0:e3:d8:b3:ec:02:57:60:41:fb:4d:b2:58:45:26: + 9a:ba:35:96:6b:ff:0c:6e:68:d4:ca:69:74:cc:55:e5:ef:74: + 07:bf:8a:c0:7a:63:22:fd:06:1d:93:18:0d:c2:8f:12:45:3a: + 99:0f:7f:20:23:27:3a:8b:75:2c:26:59:37:d6:75:76:4b:4b: + 87:34:a8:99:f9:eb:90:bf:5a:e5:43:a0:fa:d6:42:cf:1e:c4: + 58:43:c2:44:3a:90:84:5c:82:83:db:6b:c4:21:e7:5e:39:ea: + ce:4f:be:28:e1:bf:48:e3:d6:fb:0a:88:92:bd:b3:df:2c:0c: + 66:d2:23:5d:03:89:5f:de:4f:ae:8f:05:47:9a:aa:84:70:b5: + 74:bd:19:2c:cd:ca:0b:f5:9a:82:5f:1b:e2:a7:c9:0f:9a:bc: + c2:69:f4:5c:f2:29:cb:de:c2:f6:63:8d:df:15:b6:34:64:0d: + aa:a3:42:32:e6:5b:eb:57:10:e3:01:b1:78:b6:fc:a2:ff:62: + 3f:ba:43:4a +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgICDKUwDQYJKoZIhvcNAQEFBQAwdzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEhMB8GA1UEAxMYQ0FTIFRlc3QgSW50ZXJt +ZWRpYXRlIENBMB4XDTExMDExOTE4MjUzOVoXDTMyMTIxNDE4MjUzOVowbzELMAkG +A1UEBhMCVVMxETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3Rl +cjEOMAwGA1UEChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRl +c3QgVXNlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKudOYgJ +1uhBZSG4jKK31mypz/X++cfs47GyFgeNKo3YvHscc4UJgsmimq8Ks07cU72fJ9Ft +keR3jf68dDYmVAiB5h901lqkQX2Vk1xbFx6jO5AIli1q6ADBsTwKC5MtxAAn1a3t +0bJmRCxgsC+SJnvUiyQB1mLiSBcuPA0V/N9CWZAwhAuBhioBi1i6FQOtbgdYz6H3 +Lm7IXPKxrpA2Z9Q5lrgEtGaw90YquiFDRrn7k9E51v1AtNKd6dhP3K4UjnMFJopc +IVw5pcCaZhy4npRR7nGMmanCLjkAhQTRFKcjAE97UjVhCtIKZMkyE31GfTX2sTFI +OGBqm7W/PSM1kSECAwEAAaOBzTCByjAdBgNVHQ4EFgQUGRt4jZNisklmkFVuULPQ +r2HrOfMwgZoGA1UdIwSBkjCBj4AUEjitBchZsRCf/6hrLs4DUv9Z2H+hc6RxMG8x +CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1p +bnN0ZXIxDjAMBgNVBAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxGTAXBgNVBAMTEENB +UyBUZXN0IFJvb3QgQ0GCAgylMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBAAWVateotcyUolQaJwREVfLNL9MV7lyAmWMUtCOG/myQ3jqsJmic76tVVgB3 +TtyTMjLreNoq+4YvetDj2LPsAldgQftNslhFJpq6NZZr/wxuaNTKaXTMVeXvdAe/ +isB6YyL9Bh2TGA3CjxJFOpkPfyAjJzqLdSwmWTfWdXZLS4c0qJn565C/WuVDoPrW +Qs8exFhDwkQ6kIRcgoPba8Qh51456s5Pvijhv0jj1vsKiJK9s98sDGbSI10DiV/e +T66PBUeaqoRwtXS9GSzNygv1moJfG+KnyQ+avMJp9FzyKcvewvZjjd8VtjRkDaqj +QjLmW+tXEOMBsXi2/KL/Yj+6Q0o= +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/etc/testCA/userCA/crl/crl-02.pem b/cas-server-support-x509/etc/testCA/userCA/crl/crl-02.pem new file mode 100644 index 000000000000..49aebe7331dd --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/crl/crl-02.pem @@ -0,0 +1,13 @@ +-----BEGIN X509 CRL----- +MIICBzCB8AIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJVUzERMA8GA1UE +CBMIQ29sb3JhZG8xFDASBgNVBAcTC1dlc3RtaW5zdGVyMQ4wDAYDVQQKEwVKYXNp +ZzEMMAoGA1UECxMDQ0FTMRkwFwYDVQQDExBDQVMgVGVzdCBVc2VyIENBFw0xMTAx +MTkxODQ5NDFaFw0xMTAyMTgxODQ5NDFaMD0wOwICDKcXDTExMDExOTE4NDEzNlow +JjAKBgNVHRUEAwoBATAYBgNVHRgEERgPMjAxMDAxMTkxOTAwMDBaoA4wDDAKBgNV +HRQEAwIBAjANBgkqhkiG9w0BAQUFAAOCAQEAEm0Yhtqwx/I10F/AdvvwHwswpsRj +Mxv+ICeWw1lNjl7n2i9yqq+EZN4pv1PqW9bENqjbYTawLRqQvuLSCPsh9r/JRVGY +hT1uZ/F8AgT/7QEbpEQ9w1pA3ZYj3fOtrhulVNbFW+uH70BXTni5OFo2al14kN89 +jedqgCou7fP8itP+zlQ3J/RLkiHwJxuBIttPh4ala0Z3cVTaeG3MoAtbjPm2FGK8 +bwSta9S+E1sTuYsqRBEepHshwo5kGf0aZjxMrAhx0a1mw49y9Ad7ckWe4HClmchg +SLaXFZumcMCR3Sdx4VNA/2ZKCYdaroyU2BvvQZ4rzT7dvtc5HXhsiHEA7A== +-----END X509 CRL----- diff --git a/cas-server-support-x509/etc/testCA/userCA/crlnumber b/cas-server-support-x509/etc/testCA/userCA/crlnumber new file mode 100644 index 000000000000..eb589e9da289 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/crlnumber @@ -0,0 +1 @@ +0B diff --git a/cas-server-support-x509/etc/testCA/userCA/crlnumber.old b/cas-server-support-x509/etc/testCA/userCA/crlnumber.old new file mode 100644 index 000000000000..d9bb888f803b --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/crlnumber.old @@ -0,0 +1 @@ +0A diff --git a/cas-server-support-x509/etc/testCA/userCA/index.txt b/cas-server-support-x509/etc/testCA/userCA/index.txt new file mode 100644 index 000000000000..5fb9add87021 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/index.txt @@ -0,0 +1,9 @@ +V 321214182757Z 0CA5 unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Alice +V 100119190000Z 0CA6 unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Handyman Bob +R 321214184006Z 110119184136Z,keyTime,20100119190000Z 0CA7 unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Mallory +V 321219185645Z 0CA8 unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Albert +V 321219190010Z 0CA9 unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Malificent +V 321221160057Z 0CAA unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Anthony +R 321221161023Z 110126161056Z,keyCompromise 0CAB unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Marauder +R 321221162127Z 110126162153Z,keyCompromise 0CAC unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Marauder +R 321226170329Z 110131170412Z,keyCompromise 0CAD unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Malverde diff --git a/cas-server-support-x509/etc/testCA/userCA/index.txt.attr b/cas-server-support-x509/etc/testCA/userCA/index.txt.attr new file mode 100644 index 000000000000..8f7e63a3475c --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/index.txt.attr @@ -0,0 +1 @@ +unique_subject = yes diff --git a/cas-server-support-x509/etc/testCA/userCA/index.txt.attr.old b/cas-server-support-x509/etc/testCA/userCA/index.txt.attr.old new file mode 100644 index 000000000000..8f7e63a3475c --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/index.txt.attr.old @@ -0,0 +1 @@ +unique_subject = yes diff --git a/cas-server-support-x509/etc/testCA/userCA/index.txt.old b/cas-server-support-x509/etc/testCA/userCA/index.txt.old new file mode 100644 index 000000000000..b0931bb1365c --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/index.txt.old @@ -0,0 +1,9 @@ +V 321214182757Z 0CA5 unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Alice +V 100119190000Z 0CA6 unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Handyman Bob +R 321214184006Z 110119184136Z,keyTime,20100119190000Z 0CA7 unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Mallory +V 321219185645Z 0CA8 unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Albert +V 321219190010Z 0CA9 unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Malificent +V 321221160057Z 0CAA unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Anthony +R 321221161023Z 110126161056Z,keyCompromise 0CAB unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Marauder +R 321221162127Z 110126162153Z,keyCompromise 0CAC unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Marauder +V 321226170329Z 0CAD unknown /C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=Malverde diff --git a/cas-server-support-x509/etc/testCA/userCA/newcerts/0CA5.pem b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CA5.pem new file mode 100644 index 000000000000..9815bba869c9 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CA5.pem @@ -0,0 +1,82 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3237 (0xca5) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Validity + Not Before: Jan 19 18:27:57 2011 GMT + Not After : Dec 14 18:27:57 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=Alice + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:7b:c9:05:71:72:77:e4:7a:9b:3f:5f:5b:5d: + 2c:84:65:8a:3e:45:d5:29:71:cd:a0:01:46:f0:76: + a3:17:52:14:70:1c:68:11:b5:1e:de:4f:2a:c5:72: + d9:c5:dd:f3:e2:f3:5b:4b:c0:f1:92:29:3b:8a:1f: + 0a:56:39:fe:02:28:cc:32:76:81:d8:db:3e:21:62: + dd:89:24:07:0b:d9:3a:ae:19:78:61:a0:b3:d5:ed: + e0:00:02:87:cb:ff:79:e6:e9:b0:b7:38:a6:db:78: + e1:cc:60:dd:6b:d7:4a:97:80:81:76:3c:6d:91:e4: + de:09:cf:a0:e6:75:29:ba:c6:d1:9c:7d:90:5d:da: + af:e4:a0:9e:fa:7b:2a:9e:da:e4:d4:64:1e:4c:25: + 77:f0:3c:f5:ea:7a:8c:e8:9f:ed:ab:66:ee:c3:c7: + 72:8c:25:30:82:7e:3f:28:22:a1:b5:6a:1d:64:8f: + 8f:45:62:33:2e:f6:bb:0b:88:93:b7:0a:45:55:d6: + 2d:84:72:62:5d:26:ec:8b:db:58:4f:b8:23:48:ed: + be:f4:55:51:8c:5b:25:2e:cf:16:12:30:d4:05:68: + e4:78:db:ab:bb:66:89:19:70:d0:a9:09:48:44:fb: + f4:7a:6c:12:55:fd:14:13:da:1f:14:c4:de:83:12: + c6:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 27:3E:80:D5:75:E0:F6:D1:53:E0:68:92:6F:67:05:DB:36:08:95:21 + X509v3 Authority Key Identifier: + keyid:19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + + Signature Algorithm: sha1WithRSAEncryption + 6a:dd:33:d9:8f:d1:8c:da:66:6c:86:eb:81:2f:b8:f7:37:8b: + ae:3c:af:a2:68:88:f7:6e:1f:53:90:6d:89:ff:6a:3e:5e:82: + 5b:79:e9:57:94:82:39:32:e2:ab:a5:ad:90:5b:cd:3a:b4:b5: + f3:96:d1:ee:ac:03:d0:69:60:42:41:a5:c7:6e:e0:68:9d:c5: + 39:4b:53:26:27:72:cc:bb:72:76:a8:c2:be:79:04:35:88:2f: + 14:f8:06:f9:f2:3d:dd:9a:8e:5a:8d:9e:8c:55:a6:02:97:73: + 2a:09:2d:4b:6a:a4:2b:19:5f:28:78:0f:40:fd:4d:8c:19:54: + 1a:1c:04:f4:15:f6:e1:8a:59:9f:99:80:a0:00:7a:56:b5:77: + 8b:c8:5f:67:ed:28:8a:3d:3e:69:08:88:71:ac:37:a4:a2:85: + 1c:6e:a3:b4:bf:ad:7e:43:f9:ca:f8:50:37:10:4a:42:53:5c: + 35:04:68:91:5e:fe:c4:b4:66:d2:5d:bd:50:58:ba:5d:42:04: + 99:2a:1b:a2:be:ff:ac:49:f4:0e:2d:e1:a3:20:21:d5:bf:f5: + bb:f1:e1:e0:52:3e:d4:fe:f3:0d:f4:ca:2e:86:ae:15:95:b7: + 69:cc:9a:c4:40:4e:85:c7:fc:83:32:09:f5:ea:68:96:aa:df: + 36:96:2d:3a +-----BEGIN CERTIFICATE----- +MIIDyjCCArKgAwIBAgICDKUwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMTkxODI3NTdaFw0zMjEyMTQxODI3NTdaMGQxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxDjAMBgNVBAMTBUFsaWNlMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy3vJBXFyd+R6mz9fW10shGWKPkXVKXHN +oAFG8HajF1IUcBxoEbUe3k8qxXLZxd3z4vNbS8Dxkik7ih8KVjn+AijMMnaB2Ns+ +IWLdiSQHC9k6rhl4YaCz1e3gAAKHy/955umwtzim23jhzGDda9dKl4CBdjxtkeTe +Cc+g5nUpusbRnH2QXdqv5KCe+nsqntrk1GQeTCV38Dz16nqM6J/tq2buw8dyjCUw +gn4/KCKhtWodZI+PRWIzLva7C4iTtwpFVdYthHJiXSbsi9tYT7gjSO2+9FVRjFsl +Ls8WEjDUBWjkeNuru2aJGXDQqQlIRPv0emwSVf0UE9ofFMTegxLGAQIDAQABo3sw +eTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBD +ZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUJz6A1XXg9tFT4GiSb2cF2zYIlSEwHwYDVR0j +BBgwFoAUGRt4jZNisklmkFVuULPQr2HrOfMwDQYJKoZIhvcNAQEFBQADggEBAGrd +M9mP0YzaZmyG64EvuPc3i648r6JoiPduH1OQbYn/aj5eglt56VeUgjky4qulrZBb +zTq0tfOW0e6sA9BpYEJBpcdu4GidxTlLUyYncsy7cnaowr55BDWILxT4BvnyPd2a +jlqNnoxVpgKXcyoJLUtqpCsZXyh4D0D9TYwZVBocBPQV9uGKWZ+ZgKAAela1d4vI +X2ftKIo9PmkIiHGsN6SihRxuo7S/rX5D+cr4UDcQSkJTXDUEaJFe/sS0ZtJdvVBY +ul1CBJkqG6K+/6xJ9A4t4aMgIdW/9bvx4eBSPtT+8w30yi6GrhWVt2nMmsRAToXH +/IMyCfXqaJaq3zaWLTo= +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/etc/testCA/userCA/newcerts/0CA6.pem b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CA6.pem new file mode 100644 index 000000000000..a8d1e4707cbf --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CA6.pem @@ -0,0 +1,82 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3238 (0xca6) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Validity + Not Before: Jan 19 18:38:13 2011 GMT + Not After : Jan 19 19:00:00 2010 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=Handyman Bob + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:7b:c9:05:71:72:77:e4:7a:9b:3f:5f:5b:5d: + 2c:84:65:8a:3e:45:d5:29:71:cd:a0:01:46:f0:76: + a3:17:52:14:70:1c:68:11:b5:1e:de:4f:2a:c5:72: + d9:c5:dd:f3:e2:f3:5b:4b:c0:f1:92:29:3b:8a:1f: + 0a:56:39:fe:02:28:cc:32:76:81:d8:db:3e:21:62: + dd:89:24:07:0b:d9:3a:ae:19:78:61:a0:b3:d5:ed: + e0:00:02:87:cb:ff:79:e6:e9:b0:b7:38:a6:db:78: + e1:cc:60:dd:6b:d7:4a:97:80:81:76:3c:6d:91:e4: + de:09:cf:a0:e6:75:29:ba:c6:d1:9c:7d:90:5d:da: + af:e4:a0:9e:fa:7b:2a:9e:da:e4:d4:64:1e:4c:25: + 77:f0:3c:f5:ea:7a:8c:e8:9f:ed:ab:66:ee:c3:c7: + 72:8c:25:30:82:7e:3f:28:22:a1:b5:6a:1d:64:8f: + 8f:45:62:33:2e:f6:bb:0b:88:93:b7:0a:45:55:d6: + 2d:84:72:62:5d:26:ec:8b:db:58:4f:b8:23:48:ed: + be:f4:55:51:8c:5b:25:2e:cf:16:12:30:d4:05:68: + e4:78:db:ab:bb:66:89:19:70:d0:a9:09:48:44:fb: + f4:7a:6c:12:55:fd:14:13:da:1f:14:c4:de:83:12: + c6:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 27:3E:80:D5:75:E0:F6:D1:53:E0:68:92:6F:67:05:DB:36:08:95:21 + X509v3 Authority Key Identifier: + keyid:19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + + Signature Algorithm: sha1WithRSAEncryption + 49:29:38:5f:a4:b4:08:06:70:4f:e7:de:35:f5:4f:74:b3:50: + a6:ca:3e:7c:67:f6:1e:65:ca:e2:9a:32:3c:58:28:10:33:36: + 18:19:19:05:38:45:16:53:c2:54:bd:2c:3f:82:c6:59:f6:ec: + c8:13:4e:b3:29:8e:fd:c0:ee:0f:68:26:2d:f6:35:97:a6:0f: + 6c:7f:e9:03:a1:bb:5c:8a:52:92:90:7f:40:a5:ff:b4:99:81: + 9d:c3:71:f6:fd:2f:c4:a2:11:88:fa:79:22:3f:a6:f7:2a:62: + fa:db:6e:43:a9:30:2d:96:c3:2d:3a:be:79:f7:ae:90:22:23: + e8:7b:24:5f:63:00:38:30:4b:db:22:d8:84:48:e1:2a:5f:d5: + 5f:8b:3c:c0:9d:8b:29:45:2d:b5:33:7f:e6:46:38:ea:f4:65: + f3:d7:29:0a:f9:23:62:d7:ad:93:ae:04:fb:c1:fa:5b:50:80: + ba:2d:05:d0:81:47:d1:c6:c0:4e:85:74:b0:9e:14:ec:73:79: + 3d:75:f5:bb:2b:dd:16:5c:8b:5f:65:c0:7b:c3:25:83:c4:3b: + b5:eb:c3:72:53:bd:a0:bf:0f:a7:3e:2c:64:21:2f:dc:c2:5c: + b6:eb:ee:10:37:d2:3a:19:96:b1:11:41:06:77:0d:eb:3b:b5: + 2c:26:ce:67 +-----BEGIN CERTIFICATE----- +MIID0TCCArmgAwIBAgICDKYwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMTkxODM4MTNaFw0xMDAxMTkxOTAwMDBaMGsxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxFTATBgNVBAMTDEhhbmR5bWFuIEJvYjCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMt7yQVxcnfkeps/X1tdLIRl +ij5F1SlxzaABRvB2oxdSFHAcaBG1Ht5PKsVy2cXd8+LzW0vA8ZIpO4ofClY5/gIo +zDJ2gdjbPiFi3YkkBwvZOq4ZeGGgs9Xt4AACh8v/eebpsLc4ptt44cxg3WvXSpeA +gXY8bZHk3gnPoOZ1KbrG0Zx9kF3ar+Sgnvp7Kp7a5NRkHkwld/A89ep6jOif7atm +7sPHcowlMIJ+PygiobVqHWSPj0ViMy72uwuIk7cKRVXWLYRyYl0m7IvbWE+4I0jt +vvRVUYxbJS7PFhIw1AVo5Hjbq7tmiRlw0KkJSET79HpsElX9FBPaHxTE3oMSxgEC +AwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5l +cmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFCc+gNV14PbRU+Bokm9nBds2CJUh +MB8GA1UdIwQYMBaAFBkbeI2TYrJJZpBVblCz0K9h6znzMA0GCSqGSIb3DQEBBQUA +A4IBAQBJKThfpLQIBnBP59419U90s1Cmyj58Z/YeZcrimjI8WCgQMzYYGRkFOEUW +U8JUvSw/gsZZ9uzIE06zKY79wO4PaCYt9jWXpg9sf+kDobtcilKSkH9Apf+0mYGd +w3H2/S/EohGI+nkiP6b3KmL6225DqTAtlsMtOr55966QIiPoeyRfYwA4MEvbItiE +SOEqX9VfizzAnYspRS21M3/mRjjq9GXz1ykK+SNi162TrgT7wfpbUIC6LQXQgUfR +xsBOhXSwnhTsc3k9dfW7K90WXItfZcB7wyWDxDu168NyU72gvw+nPixkIS/cwly2 +6+4QN9I6GZaxEUEGdw3rO7UsJs5n +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/etc/testCA/userCA/newcerts/0CA7.pem b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CA7.pem new file mode 100644 index 000000000000..6d153c1e932f --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CA7.pem @@ -0,0 +1,82 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3239 (0xca7) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Validity + Not Before: Jan 19 18:40:06 2011 GMT + Not After : Dec 14 18:40:06 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=Mallory + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:7b:c9:05:71:72:77:e4:7a:9b:3f:5f:5b:5d: + 2c:84:65:8a:3e:45:d5:29:71:cd:a0:01:46:f0:76: + a3:17:52:14:70:1c:68:11:b5:1e:de:4f:2a:c5:72: + d9:c5:dd:f3:e2:f3:5b:4b:c0:f1:92:29:3b:8a:1f: + 0a:56:39:fe:02:28:cc:32:76:81:d8:db:3e:21:62: + dd:89:24:07:0b:d9:3a:ae:19:78:61:a0:b3:d5:ed: + e0:00:02:87:cb:ff:79:e6:e9:b0:b7:38:a6:db:78: + e1:cc:60:dd:6b:d7:4a:97:80:81:76:3c:6d:91:e4: + de:09:cf:a0:e6:75:29:ba:c6:d1:9c:7d:90:5d:da: + af:e4:a0:9e:fa:7b:2a:9e:da:e4:d4:64:1e:4c:25: + 77:f0:3c:f5:ea:7a:8c:e8:9f:ed:ab:66:ee:c3:c7: + 72:8c:25:30:82:7e:3f:28:22:a1:b5:6a:1d:64:8f: + 8f:45:62:33:2e:f6:bb:0b:88:93:b7:0a:45:55:d6: + 2d:84:72:62:5d:26:ec:8b:db:58:4f:b8:23:48:ed: + be:f4:55:51:8c:5b:25:2e:cf:16:12:30:d4:05:68: + e4:78:db:ab:bb:66:89:19:70:d0:a9:09:48:44:fb: + f4:7a:6c:12:55:fd:14:13:da:1f:14:c4:de:83:12: + c6:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 27:3E:80:D5:75:E0:F6:D1:53:E0:68:92:6F:67:05:DB:36:08:95:21 + X509v3 Authority Key Identifier: + keyid:19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + + Signature Algorithm: sha1WithRSAEncryption + 61:09:50:8d:2b:92:61:e5:ac:5a:f9:cf:9f:95:fb:ae:de:de: + 59:81:9f:1a:75:52:40:f4:85:54:d2:1a:3e:d7:39:8f:d4:83: + 26:13:2d:2a:a5:dc:5a:9e:43:d6:b3:78:00:92:c8:a8:7b:4d: + 0c:38:6d:94:13:d4:87:91:1d:4e:5f:f1:2d:6b:4f:45:c8:7e: + 88:01:20:08:8c:f9:6a:4a:ed:54:58:fd:3c:f8:d7:c8:ca:98: + 7f:29:9b:2c:fb:3b:bc:7d:e0:e8:2b:df:07:02:44:7a:c0:f8: + 40:6e:9f:64:68:7f:7f:a4:b7:2a:05:75:4a:8e:b7:c5:e0:15: + 18:94:21:27:ea:b2:cd:b0:5c:4e:f0:fc:98:2c:4f:20:a1:99: + 21:0e:18:24:38:6b:6c:f8:02:45:25:98:05:8a:b9:01:c9:5f: + 21:b0:29:88:51:f0:8a:46:a9:c3:9b:6b:39:1e:c9:6f:b4:96: + 1c:15:d1:6e:df:00:36:f1:3a:35:da:e5:ea:58:15:c4:e1:1c: + b3:11:d2:b7:c1:80:23:1f:b4:3f:6c:57:1d:67:3b:cb:94:f8: + 06:7f:21:48:39:a9:4e:f7:b3:48:6c:9c:a7:d4:c6:cc:73:28: + 52:ab:06:d5:c9:89:c9:30:7d:41:01:55:ca:2a:7c:f6:aa:8f: + 28:70:f0:13 +-----BEGIN CERTIFICATE----- +MIIDzDCCArSgAwIBAgICDKcwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMTkxODQwMDZaFw0zMjEyMTQxODQwMDZaMGYxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxEDAOBgNVBAMTB01hbGxvcnkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLe8kFcXJ35HqbP19bXSyEZYo+RdUp +cc2gAUbwdqMXUhRwHGgRtR7eTyrFctnF3fPi81tLwPGSKTuKHwpWOf4CKMwydoHY +2z4hYt2JJAcL2TquGXhhoLPV7eAAAofL/3nm6bC3OKbbeOHMYN1r10qXgIF2PG2R +5N4Jz6DmdSm6xtGcfZBd2q/koJ76eyqe2uTUZB5MJXfwPPXqeozon+2rZu7Dx3KM +JTCCfj8oIqG1ah1kj49FYjMu9rsLiJO3CkVV1i2EcmJdJuyL21hPuCNI7b70VVGM +WyUuzxYSMNQFaOR426u7ZokZcNCpCUhE+/R6bBJV/RQT2h8UxN6DEsYBAgMBAAGj +ezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVk +IENlcnRpZmljYXRlMB0GA1UdDgQWBBQnPoDVdeD20VPgaJJvZwXbNgiVITAfBgNV +HSMEGDAWgBQZG3iNk2KySWaQVW5Qs9CvYes58zANBgkqhkiG9w0BAQUFAAOCAQEA +YQlQjSuSYeWsWvnPn5X7rt7eWYGfGnVSQPSFVNIaPtc5j9SDJhMtKqXcWp5D1rN4 +AJLIqHtNDDhtlBPUh5EdTl/xLWtPRch+iAEgCIz5akrtVFj9PPjXyMqYfymbLPs7 +vH3g6CvfBwJEesD4QG6fZGh/f6S3KgV1So63xeAVGJQhJ+qyzbBcTvD8mCxPIKGZ +IQ4YJDhrbPgCRSWYBYq5AclfIbApiFHwikapw5trOR7Jb7SWHBXRbt8ANvE6Ndrl +6lgVxOEcsxHSt8GAIx+0P2xXHWc7y5T4Bn8hSDmpTvezSGycp9TGzHMoUqsG1cmJ +yTB9QQFVyip89qqPKHDwEw== +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/etc/testCA/userCA/newcerts/0CA8.pem b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CA8.pem new file mode 100644 index 000000000000..ee510cdf507e --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CA8.pem @@ -0,0 +1,84 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3240 (0xca8) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Validity + Not Before: Jan 24 18:56:45 2011 GMT + Not After : Dec 19 18:56:45 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=Albert + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:7b:c9:05:71:72:77:e4:7a:9b:3f:5f:5b:5d: + 2c:84:65:8a:3e:45:d5:29:71:cd:a0:01:46:f0:76: + a3:17:52:14:70:1c:68:11:b5:1e:de:4f:2a:c5:72: + d9:c5:dd:f3:e2:f3:5b:4b:c0:f1:92:29:3b:8a:1f: + 0a:56:39:fe:02:28:cc:32:76:81:d8:db:3e:21:62: + dd:89:24:07:0b:d9:3a:ae:19:78:61:a0:b3:d5:ed: + e0:00:02:87:cb:ff:79:e6:e9:b0:b7:38:a6:db:78: + e1:cc:60:dd:6b:d7:4a:97:80:81:76:3c:6d:91:e4: + de:09:cf:a0:e6:75:29:ba:c6:d1:9c:7d:90:5d:da: + af:e4:a0:9e:fa:7b:2a:9e:da:e4:d4:64:1e:4c:25: + 77:f0:3c:f5:ea:7a:8c:e8:9f:ed:ab:66:ee:c3:c7: + 72:8c:25:30:82:7e:3f:28:22:a1:b5:6a:1d:64:8f: + 8f:45:62:33:2e:f6:bb:0b:88:93:b7:0a:45:55:d6: + 2d:84:72:62:5d:26:ec:8b:db:58:4f:b8:23:48:ed: + be:f4:55:51:8c:5b:25:2e:cf:16:12:30:d4:05:68: + e4:78:db:ab:bb:66:89:19:70:d0:a9:09:48:44:fb: + f4:7a:6c:12:55:fd:14:13:da:1f:14:c4:de:83:12: + c6:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 27:3E:80:D5:75:E0:F6:D1:53:E0:68:92:6F:67:05:DB:36:08:95:21 + X509v3 Authority Key Identifier: + keyid:19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + + Signature Algorithm: sha1WithRSAEncryption + a0:9a:05:61:8d:6d:9c:03:23:11:59:dc:98:62:eb:49:2a:ea: + 60:d8:31:f3:c2:5a:fb:49:36:3d:b7:0b:2a:16:91:7b:31:ad: + 25:10:b9:f1:cb:d0:89:c2:90:7d:59:9a:12:e1:f6:97:e9:93: + 5b:14:a6:2a:bd:70:f2:38:19:fc:22:44:76:5a:f9:45:1a:2d: + 5a:84:fe:ab:ca:76:00:94:1d:eb:c6:fb:52:bd:ef:97:97:53: + a3:0f:90:ad:1e:d8:30:ed:f4:90:22:3c:eb:08:95:ac:fe:0a: + 98:72:12:6a:84:60:85:86:44:d4:bc:22:ed:92:9f:05:84:5d: + 34:9a:01:84:ea:a0:06:86:5f:91:50:28:fb:8f:3f:8a:6e:a8: + 35:e9:32:40:f1:04:61:88:25:91:ca:59:72:63:75:17:a7:93: + 56:98:a1:7e:51:3f:52:47:91:ff:be:57:fb:ff:2a:7a:ee:1d: + e1:25:ae:97:d1:13:fd:41:03:3c:91:91:44:df:b3:35:14:83: + ab:36:c6:c7:15:f1:32:de:ba:ba:6e:a5:78:65:f5:4a:8d:dc: + c3:84:1a:c2:a9:cd:8a:49:2f:5f:f0:b1:0f:09:09:bc:1c:bd: + 22:87:37:05:3d:71:85:37:55:b4:eb:05:7e:cb:60:fc:ce:5b: + 23:3e:0d:55 +-----BEGIN CERTIFICATE----- +MIID2jCCAsKgAwIBAgICDKgwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMjQxODU2NDVaFw0zMjEyMTkxODU2NDVaMGUxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxDzANBgNVBAMTBkFsYmVydDCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMt7yQVxcnfkeps/X1tdLIRlij5F1Slx +zaABRvB2oxdSFHAcaBG1Ht5PKsVy2cXd8+LzW0vA8ZIpO4ofClY5/gIozDJ2gdjb +PiFi3YkkBwvZOq4ZeGGgs9Xt4AACh8v/eebpsLc4ptt44cxg3WvXSpeAgXY8bZHk +3gnPoOZ1KbrG0Zx9kF3ar+Sgnvp7Kp7a5NRkHkwld/A89ep6jOif7atm7sPHcowl +MIJ+PygiobVqHWSPj0ViMy72uwuIk7cKRVXWLYRyYl0m7IvbWE+4I0jtvvRVUYxb +JS7PFhIw1AVo5Hjbq7tmiRlw0KkJSET79HpsElX9FBPaHxTE3oMSxgECAwEAAaOB +iTCBhjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAsBglghkgBhvhCAQ0EHxYdT3Bl +blNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFCc+gNV14PbRU+Bo +km9nBds2CJUhMB8GA1UdIwQYMBaAFBkbeI2TYrJJZpBVblCz0K9h6znzMA0GCSqG +SIb3DQEBBQUAA4IBAQCgmgVhjW2cAyMRWdyYYutJKupg2DHzwlr7STY9twsqFpF7 +Ma0lELnxy9CJwpB9WZoS4faX6ZNbFKYqvXDyOBn8IkR2WvlFGi1ahP6rynYAlB3r +xvtSve+Xl1OjD5CtHtgw7fSQIjzrCJWs/gqYchJqhGCFhkTUvCLtkp8FhF00mgGE +6qAGhl+RUCj7jz+Kbqg16TJA8QRhiCWRyllyY3UXp5NWmKF+UT9SR5H/vlf7/yp6 +7h3hJa6X0RP9QQM8kZFE37M1FIOrNsbHFfEy3rq6bqV4ZfVKjdzDhBrCqc2KSS9f +8LEPCQm8HL0ihzcFPXGFN1W06wV+y2D8zlsjPg1V +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/etc/testCA/userCA/newcerts/0CA9.pem b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CA9.pem new file mode 100644 index 000000000000..b85cabe7ef66 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CA9.pem @@ -0,0 +1,84 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3241 (0xca9) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Validity + Not Before: Jan 24 19:00:10 2011 GMT + Not After : Dec 19 19:00:10 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=Malificent + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:7b:c9:05:71:72:77:e4:7a:9b:3f:5f:5b:5d: + 2c:84:65:8a:3e:45:d5:29:71:cd:a0:01:46:f0:76: + a3:17:52:14:70:1c:68:11:b5:1e:de:4f:2a:c5:72: + d9:c5:dd:f3:e2:f3:5b:4b:c0:f1:92:29:3b:8a:1f: + 0a:56:39:fe:02:28:cc:32:76:81:d8:db:3e:21:62: + dd:89:24:07:0b:d9:3a:ae:19:78:61:a0:b3:d5:ed: + e0:00:02:87:cb:ff:79:e6:e9:b0:b7:38:a6:db:78: + e1:cc:60:dd:6b:d7:4a:97:80:81:76:3c:6d:91:e4: + de:09:cf:a0:e6:75:29:ba:c6:d1:9c:7d:90:5d:da: + af:e4:a0:9e:fa:7b:2a:9e:da:e4:d4:64:1e:4c:25: + 77:f0:3c:f5:ea:7a:8c:e8:9f:ed:ab:66:ee:c3:c7: + 72:8c:25:30:82:7e:3f:28:22:a1:b5:6a:1d:64:8f: + 8f:45:62:33:2e:f6:bb:0b:88:93:b7:0a:45:55:d6: + 2d:84:72:62:5d:26:ec:8b:db:58:4f:b8:23:48:ed: + be:f4:55:51:8c:5b:25:2e:cf:16:12:30:d4:05:68: + e4:78:db:ab:bb:66:89:19:70:d0:a9:09:48:44:fb: + f4:7a:6c:12:55:fd:14:13:da:1f:14:c4:de:83:12: + c6:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Non Repudiation + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 27:3E:80:D5:75:E0:F6:D1:53:E0:68:92:6F:67:05:DB:36:08:95:21 + X509v3 Authority Key Identifier: + keyid:19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + + Signature Algorithm: sha1WithRSAEncryption + 79:7d:9c:74:de:69:3e:3b:83:a7:a6:b0:4c:c0:39:49:9d:b7: + 1c:03:a2:d2:ae:61:75:d6:7d:2f:75:37:ec:bd:ca:eb:eb:62: + ec:23:62:c7:11:e8:7a:a5:46:f8:9f:a4:ad:75:b7:1f:80:08: + da:d6:dc:ff:48:b5:7e:7d:de:94:d7:c3:d5:36:a0:a7:3a:33: + 6c:11:da:3c:33:59:95:ab:e1:52:f4:de:a9:37:33:b1:1f:ff: + ae:3b:2a:18:d4:bf:42:a1:86:59:f1:be:a0:62:df:47:39:e0: + 24:91:80:a6:3f:43:7e:6e:e9:af:a8:98:67:24:92:fb:66:06: + e5:26:c6:85:f1:fd:c5:d6:92:cf:e6:6e:a2:8a:b4:3e:22:6b: + a4:20:e3:13:eb:ee:d4:1c:b1:e9:21:70:30:20:af:c8:87:1f: + 37:fa:81:7e:3a:09:23:e9:43:17:a5:4b:40:17:f7:f5:67:2d: + a2:66:af:48:8c:bc:84:ad:99:8c:97:0f:e2:ac:97:96:5d:73: + 9b:76:99:8d:62:b8:eb:63:d9:94:79:14:d7:8c:16:91:ed:a1: + 5e:e6:0c:b9:8f:75:39:a0:1a:d6:69:0e:27:46:d7:8e:1c:71: + 7b:b6:4c:7f:df:83:34:d9:ac:c1:3d:45:a1:4f:c9:9e:06:46: + 27:d3:bc:b3 +-----BEGIN CERTIFICATE----- +MIID3jCCAsagAwIBAgICDKkwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMjQxOTAwMTBaFw0zMjEyMTkxOTAwMTBaMGkxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxEzARBgNVBAMTCk1hbGlmaWNlbnQwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLe8kFcXJ35HqbP19bXSyEZYo+ +RdUpcc2gAUbwdqMXUhRwHGgRtR7eTyrFctnF3fPi81tLwPGSKTuKHwpWOf4CKMwy +doHY2z4hYt2JJAcL2TquGXhhoLPV7eAAAofL/3nm6bC3OKbbeOHMYN1r10qXgIF2 +PG2R5N4Jz6DmdSm6xtGcfZBd2q/koJ76eyqe2uTUZB5MJXfwPPXqeozon+2rZu7D +x3KMJTCCfj8oIqG1ah1kj49FYjMu9rsLiJO3CkVV1i2EcmJdJuyL21hPuCNI7b70 +VVGMWyUuzxYSMNQFaOR426u7ZokZcNCpCUhE+/R6bBJV/RQT2h8UxN6DEsYBAgMB +AAGjgYkwgYYwCQYDVR0TBAIwADALBgNVHQ8EBAMCBkAwLAYJYIZIAYb4QgENBB8W +HU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQnPoDVdeD2 +0VPgaJJvZwXbNgiVITAfBgNVHSMEGDAWgBQZG3iNk2KySWaQVW5Qs9CvYes58zAN +BgkqhkiG9w0BAQUFAAOCAQEAeX2cdN5pPjuDp6awTMA5SZ23HAOi0q5hddZ9L3U3 +7L3K6+ti7CNixxHoeqVG+J+krXW3H4AI2tbc/0i1fn3elNfD1TagpzozbBHaPDNZ +lavhUvTeqTczsR//rjsqGNS/QqGGWfG+oGLfRzngJJGApj9Dfm7pr6iYZySS+2YG +5SbGhfH9xdaSz+Zuooq0PiJrpCDjE+vu1Byx6SFwMCCvyIcfN/qBfjoJI+lDF6VL +QBf39WctomavSIy8hK2ZjJcP4qyXll1zm3aZjWK462PZlHkU14wWke2hXuYMuY91 +OaAa1mkOJ0bXjhxxe7ZMf9+DNNmswT1FoU/JngZGJ9O8sw== +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/etc/testCA/userCA/newcerts/0CAA.pem b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CAA.pem new file mode 100644 index 000000000000..ad907c10b538 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CAA.pem @@ -0,0 +1,88 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3242 (0xcaa) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Validity + Not Before: Jan 26 16:00:57 2011 GMT + Not After : Dec 21 16:00:57 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=Anthony + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:7b:c9:05:71:72:77:e4:7a:9b:3f:5f:5b:5d: + 2c:84:65:8a:3e:45:d5:29:71:cd:a0:01:46:f0:76: + a3:17:52:14:70:1c:68:11:b5:1e:de:4f:2a:c5:72: + d9:c5:dd:f3:e2:f3:5b:4b:c0:f1:92:29:3b:8a:1f: + 0a:56:39:fe:02:28:cc:32:76:81:d8:db:3e:21:62: + dd:89:24:07:0b:d9:3a:ae:19:78:61:a0:b3:d5:ed: + e0:00:02:87:cb:ff:79:e6:e9:b0:b7:38:a6:db:78: + e1:cc:60:dd:6b:d7:4a:97:80:81:76:3c:6d:91:e4: + de:09:cf:a0:e6:75:29:ba:c6:d1:9c:7d:90:5d:da: + af:e4:a0:9e:fa:7b:2a:9e:da:e4:d4:64:1e:4c:25: + 77:f0:3c:f5:ea:7a:8c:e8:9f:ed:ab:66:ee:c3:c7: + 72:8c:25:30:82:7e:3f:28:22:a1:b5:6a:1d:64:8f: + 8f:45:62:33:2e:f6:bb:0b:88:93:b7:0a:45:55:d6: + 2d:84:72:62:5d:26:ec:8b:db:58:4f:b8:23:48:ed: + be:f4:55:51:8c:5b:25:2e:cf:16:12:30:d4:05:68: + e4:78:db:ab:bb:66:89:19:70:d0:a9:09:48:44:fb: + f4:7a:6c:12:55:fd:14:13:da:1f:14:c4:de:83:12: + c6:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 27:3E:80:D5:75:E0:F6:D1:53:E0:68:92:6F:67:05:DB:36:08:95:21 + X509v3 Authority Key Identifier: + keyid:19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + + X509v3 CRL Distribution Points: + URI:http://localhost:8085/ca.crl + URI:http://example.com/ca.crl + + Signature Algorithm: sha1WithRSAEncryption + 1f:e3:c6:d6:87:cb:3f:ff:5f:c4:6f:b8:eb:69:8e:15:0c:d4: + 95:73:17:6a:5e:5d:39:ab:67:12:c8:ec:f5:bb:49:40:e4:85: + 8e:83:db:b8:2c:0a:c8:f0:74:1d:23:32:0a:96:de:15:e9:ff: + 1b:87:aa:7b:ed:b9:d0:d1:32:d2:75:d5:83:be:87:b5:f9:7c: + 41:23:e1:8d:01:4b:aa:4a:49:a6:d6:c7:45:13:f5:23:a5:44: + 5a:75:2f:ac:2b:11:c7:bc:a8:50:b8:9f:90:93:c6:d0:e8:df: + 6a:b8:4f:ec:4e:fd:41:b2:26:97:4a:d4:a5:5f:42:c0:ad:15: + 20:b0:92:8e:bb:a0:41:f2:92:f3:f3:83:f2:4a:97:35:c1:21: + 6f:1d:32:0f:e7:23:96:06:fc:10:4a:4b:8d:dc:48:00:30:68: + 0f:de:67:97:1d:6b:52:6e:1f:22:8b:f9:cd:55:75:33:a1:55: + 25:b8:bc:f9:90:15:80:25:79:2c:3e:3d:db:64:ae:7c:b7:5a: + da:66:4d:67:2f:15:9b:95:a6:67:cd:77:49:7f:95:b0:b7:72: + 93:a0:f9:f8:bf:70:d6:87:d5:02:26:bf:ac:3b:03:88:a9:83: + 76:72:27:f6:84:58:3f:5b:cd:db:09:aa:6b:32:49:d7:bb:d8: + 9c:8e:20:13 +-----BEGIN CERTIFICATE----- +MIIEHjCCAwagAwIBAgICDKowDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMjYxNjAwNTdaFw0zMjEyMjExNjAwNTdaMGYxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxEDAOBgNVBAMTB0FudGhvbnkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLe8kFcXJ35HqbP19bXSyEZYo+RdUp +cc2gAUbwdqMXUhRwHGgRtR7eTyrFctnF3fPi81tLwPGSKTuKHwpWOf4CKMwydoHY +2z4hYt2JJAcL2TquGXhhoLPV7eAAAofL/3nm6bC3OKbbeOHMYN1r10qXgIF2PG2R +5N4Jz6DmdSm6xtGcfZBd2q/koJ76eyqe2uTUZB5MJXfwPPXqeozon+2rZu7Dx3KM +JTCCfj8oIqG1ah1kj49FYjMu9rsLiJO3CkVV1i2EcmJdJuyL21hPuCNI7b70VVGM +WyUuzxYSMNQFaOR426u7ZokZcNCpCUhE+/R6bBJV/RQT2h8UxN6DEsYBAgMBAAGj +gcwwgckwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0 +ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFCc+gNV14PbRU+Bokm9nBds2CJUhMB8G +A1UdIwQYMBaAFBkbeI2TYrJJZpBVblCz0K9h6znzME4GA1UdHwRHMEUwIqAgoB6G +HGh0dHA6Ly9sb2NhbGhvc3Q6ODA4NS9jYS5jcmwwH6AdoBuGGWh0dHA6Ly9leGFt +cGxlLmNvbS9jYS5jcmwwDQYJKoZIhvcNAQEFBQADggEBAB/jxtaHyz//X8RvuOtp +jhUM1JVzF2peXTmrZxLI7PW7SUDkhY6D27gsCsjwdB0jMgqW3hXp/xuHqnvtudDR +MtJ11YO+h7X5fEEj4Y0BS6pKSabWx0UT9SOlRFp1L6wrEce8qFC4n5CTxtDo32q4 +T+xO/UGyJpdK1KVfQsCtFSCwko67oEHykvPzg/JKlzXBIW8dMg/nI5YG/BBKS43c +SAAwaA/eZ5cda1JuHyKL+c1VdTOhVSW4vPmQFYAleSw+Pdtkrny3WtpmTWcvFZuV +pmfNd0l/lbC3cpOg+fi/cNaH1QImv6w7A4ipg3ZyJ/aEWD9bzdsJqmsySde72JyO +IBM= +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/etc/testCA/userCA/newcerts/0CAB.pem b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CAB.pem new file mode 100644 index 000000000000..1203379eeed5 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CAB.pem @@ -0,0 +1,88 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3243 (0xcab) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Validity + Not Before: Jan 26 16:10:23 2011 GMT + Not After : Dec 21 16:10:23 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=Marauder + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:7b:c9:05:71:72:77:e4:7a:9b:3f:5f:5b:5d: + 2c:84:65:8a:3e:45:d5:29:71:cd:a0:01:46:f0:76: + a3:17:52:14:70:1c:68:11:b5:1e:de:4f:2a:c5:72: + d9:c5:dd:f3:e2:f3:5b:4b:c0:f1:92:29:3b:8a:1f: + 0a:56:39:fe:02:28:cc:32:76:81:d8:db:3e:21:62: + dd:89:24:07:0b:d9:3a:ae:19:78:61:a0:b3:d5:ed: + e0:00:02:87:cb:ff:79:e6:e9:b0:b7:38:a6:db:78: + e1:cc:60:dd:6b:d7:4a:97:80:81:76:3c:6d:91:e4: + de:09:cf:a0:e6:75:29:ba:c6:d1:9c:7d:90:5d:da: + af:e4:a0:9e:fa:7b:2a:9e:da:e4:d4:64:1e:4c:25: + 77:f0:3c:f5:ea:7a:8c:e8:9f:ed:ab:66:ee:c3:c7: + 72:8c:25:30:82:7e:3f:28:22:a1:b5:6a:1d:64:8f: + 8f:45:62:33:2e:f6:bb:0b:88:93:b7:0a:45:55:d6: + 2d:84:72:62:5d:26:ec:8b:db:58:4f:b8:23:48:ed: + be:f4:55:51:8c:5b:25:2e:cf:16:12:30:d4:05:68: + e4:78:db:ab:bb:66:89:19:70:d0:a9:09:48:44:fb: + f4:7a:6c:12:55:fd:14:13:da:1f:14:c4:de:83:12: + c6:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 27:3E:80:D5:75:E0:F6:D1:53:E0:68:92:6F:67:05:DB:36:08:95:21 + X509v3 Authority Key Identifier: + keyid:19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + + X509v3 CRL Distribution Points: + URI:http://localhost:8085/ca.crl + URI:http://example.com/ca.crl + + Signature Algorithm: sha1WithRSAEncryption + 32:9e:ac:52:2a:4b:fb:be:1c:a7:c7:bf:85:3e:52:5b:28:01: + 84:07:95:5d:ee:17:c6:3d:68:3b:97:2b:b2:69:c4:82:dc:11: + 3a:20:a1:92:32:3a:ed:13:48:5a:1b:ac:25:07:80:af:42:f8: + 9d:2c:11:be:ca:77:bb:8e:01:26:95:bb:ea:f7:bd:9d:a0:c3: + fd:4e:98:02:43:b7:b6:79:03:9b:8f:f6:73:06:e9:09:2e:19: + dd:69:bf:40:22:9f:8c:ef:bd:d2:76:73:b9:c7:e5:dd:9b:fc: + 3f:8c:2d:6a:7a:c7:d2:58:31:e3:7d:b0:f0:a2:36:a0:32:19: + d7:75:7c:cb:95:a2:1d:44:4c:04:2a:80:d9:e4:55:a7:07:ef: + 24:01:3e:ad:6a:72:2b:1a:b4:ae:4c:21:fb:e5:cd:9a:e6:1d: + a4:5f:2b:3e:53:92:0a:b6:92:00:ea:cb:e4:27:fd:6a:77:ad: + d6:6a:48:57:f0:df:e2:f4:91:47:94:33:33:0d:43:da:ec:5d: + 73:8d:dd:cc:83:e5:00:f2:aa:28:29:1e:30:82:6f:c1:be:2a: + f8:9c:ea:5c:37:8d:8f:d7:cd:f5:32:c6:30:82:f7:39:a8:e0: + 7d:33:e9:11:87:40:24:31:a1:86:2d:1f:4b:fe:34:1c:ac:f0: + 8f:01:76:09 +-----BEGIN CERTIFICATE----- +MIIEHzCCAwegAwIBAgICDKswDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMjYxNjEwMjNaFw0zMjEyMjExNjEwMjNaMGcxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxETAPBgNVBAMTCE1hcmF1ZGVyMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy3vJBXFyd+R6mz9fW10shGWKPkXV +KXHNoAFG8HajF1IUcBxoEbUe3k8qxXLZxd3z4vNbS8Dxkik7ih8KVjn+AijMMnaB +2Ns+IWLdiSQHC9k6rhl4YaCz1e3gAAKHy/955umwtzim23jhzGDda9dKl4CBdjxt +keTeCc+g5nUpusbRnH2QXdqv5KCe+nsqntrk1GQeTCV38Dz16nqM6J/tq2buw8dy +jCUwgn4/KCKhtWodZI+PRWIzLva7C4iTtwpFVdYthHJiXSbsi9tYT7gjSO2+9FVR +jFslLs8WEjDUBWjkeNuru2aJGXDQqQlIRPv0emwSVf0UE9ofFMTegxLGAQIDAQAB +o4HMMIHJMAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJh +dGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQnPoDVdeD20VPgaJJvZwXbNgiVITAf +BgNVHSMEGDAWgBQZG3iNk2KySWaQVW5Qs9CvYes58zBOBgNVHR8ERzBFMCKgIKAe +hhxodHRwOi8vbG9jYWxob3N0OjgwODUvY2EuY3JsMB+gHaAbhhlodHRwOi8vZXhh +bXBsZS5jb20vY2EuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQAynqxSKkv7vhynx7+F +PlJbKAGEB5Vd7hfGPWg7lyuyacSC3BE6IKGSMjrtE0haG6wlB4CvQvidLBG+yne7 +jgEmlbvq972doMP9TpgCQ7e2eQObj/ZzBukJLhndab9AIp+M773SdnO5x+Xdm/w/ +jC1qesfSWDHjfbDwojagMhnXdXzLlaIdREwEKoDZ5FWnB+8kAT6tanIrGrSuTCH7 +5c2a5h2kXys+U5IKtpIA6svkJ/1qd63WakhX8N/i9JFHlDMzDUPa7F1zjd3Mg+UA +8qooKR4wgm/Bvir4nOpcN42P1831MsYwgvc5qOB9M+kRh0AkMaGGLR9L/jQcrPCP +AXYJ +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/etc/testCA/userCA/newcerts/0CAC.pem b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CAC.pem new file mode 100644 index 000000000000..ce100b3c66df --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CAC.pem @@ -0,0 +1,86 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3244 (0xcac) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Validity + Not Before: Jan 26 16:21:27 2011 GMT + Not After : Dec 21 16:21:27 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=Marauder + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:7b:c9:05:71:72:77:e4:7a:9b:3f:5f:5b:5d: + 2c:84:65:8a:3e:45:d5:29:71:cd:a0:01:46:f0:76: + a3:17:52:14:70:1c:68:11:b5:1e:de:4f:2a:c5:72: + d9:c5:dd:f3:e2:f3:5b:4b:c0:f1:92:29:3b:8a:1f: + 0a:56:39:fe:02:28:cc:32:76:81:d8:db:3e:21:62: + dd:89:24:07:0b:d9:3a:ae:19:78:61:a0:b3:d5:ed: + e0:00:02:87:cb:ff:79:e6:e9:b0:b7:38:a6:db:78: + e1:cc:60:dd:6b:d7:4a:97:80:81:76:3c:6d:91:e4: + de:09:cf:a0:e6:75:29:ba:c6:d1:9c:7d:90:5d:da: + af:e4:a0:9e:fa:7b:2a:9e:da:e4:d4:64:1e:4c:25: + 77:f0:3c:f5:ea:7a:8c:e8:9f:ed:ab:66:ee:c3:c7: + 72:8c:25:30:82:7e:3f:28:22:a1:b5:6a:1d:64:8f: + 8f:45:62:33:2e:f6:bb:0b:88:93:b7:0a:45:55:d6: + 2d:84:72:62:5d:26:ec:8b:db:58:4f:b8:23:48:ed: + be:f4:55:51:8c:5b:25:2e:cf:16:12:30:d4:05:68: + e4:78:db:ab:bb:66:89:19:70:d0:a9:09:48:44:fb: + f4:7a:6c:12:55:fd:14:13:da:1f:14:c4:de:83:12: + c6:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 27:3E:80:D5:75:E0:F6:D1:53:E0:68:92:6F:67:05:DB:36:08:95:21 + X509v3 Authority Key Identifier: + keyid:19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + + X509v3 CRL Distribution Points: + URI:http://localhost:8085/ca.crl + + Signature Algorithm: sha1WithRSAEncryption + 1d:ce:c3:da:f9:52:fa:8d:cb:68:be:17:2d:3d:b2:dc:86:82: + 4a:4b:db:90:95:58:21:41:93:9f:8f:86:22:00:2c:6f:ae:29: + 27:30:cc:78:d1:79:84:27:e0:44:a4:d2:af:7c:1e:1a:71:59: + 44:b0:23:ff:73:34:09:63:78:37:57:87:62:eb:2f:4b:b7:16: + 90:f9:f8:5a:4d:e5:c3:ac:e3:04:9e:ff:0c:87:aa:e1:2b:25: + ef:2d:59:03:36:53:6b:d5:54:dc:3c:39:13:88:06:e0:58:f0: + 1b:f6:10:9e:20:19:40:bb:ac:00:15:a7:09:72:3c:65:81:d9: + 40:40:68:d4:a0:7b:f5:33:8f:fa:54:13:cf:e9:ef:f6:e4:41: + aa:41:c2:95:c2:55:9f:39:78:3f:d1:1e:66:4b:de:f7:b5:53: + 83:1b:a2:0b:2e:51:9c:d1:c4:2b:75:d8:59:dc:26:64:b1:c8: + bc:29:ff:50:ca:4e:ba:b0:c7:c5:ca:d7:97:1b:ff:11:b3:fc: + cc:9e:05:6d:c6:80:2c:33:a4:d6:38:b0:31:ae:ee:90:4c:14: + 7b:b0:63:76:b5:b8:95:de:9d:72:3b:17:f7:df:e0:b0:cf:f1: + c8:46:5b:7d:4c:06:2b:a3:42:c7:99:c8:85:9f:d8:af:21:45: + f3:ee:29:78 +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgICDKwwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMjYxNjIxMjdaFw0zMjEyMjExNjIxMjdaMGcxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxETAPBgNVBAMTCE1hcmF1ZGVyMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy3vJBXFyd+R6mz9fW10shGWKPkXV +KXHNoAFG8HajF1IUcBxoEbUe3k8qxXLZxd3z4vNbS8Dxkik7ih8KVjn+AijMMnaB +2Ns+IWLdiSQHC9k6rhl4YaCz1e3gAAKHy/955umwtzim23jhzGDda9dKl4CBdjxt +keTeCc+g5nUpusbRnH2QXdqv5KCe+nsqntrk1GQeTCV38Dz16nqM6J/tq2buw8dy +jCUwgn4/KCKhtWodZI+PRWIzLva7C4iTtwpFVdYthHJiXSbsi9tYT7gjSO2+9FVR +jFslLs8WEjDUBWjkeNuru2aJGXDQqQlIRPv0emwSVf0UE9ofFMTegxLGAQIDAQAB +o4GrMIGoMAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJh +dGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQnPoDVdeD20VPgaJJvZwXbNgiVITAf +BgNVHSMEGDAWgBQZG3iNk2KySWaQVW5Qs9CvYes58zAtBgNVHR8EJjAkMCKgIKAe +hhxodHRwOi8vbG9jYWxob3N0OjgwODUvY2EuY3JsMA0GCSqGSIb3DQEBBQUAA4IB +AQAdzsPa+VL6jctovhctPbLchoJKS9uQlVghQZOfj4YiACxvriknMMx40XmEJ+BE +pNKvfB4acVlEsCP/czQJY3g3V4di6y9LtxaQ+fhaTeXDrOMEnv8Mh6rhKyXvLVkD +NlNr1VTcPDkTiAbgWPAb9hCeIBlAu6wAFacJcjxlgdlAQGjUoHv1M4/6VBPP6e/2 +5EGqQcKVwlWfOXg/0R5mS973tVODG6ILLlGc0cQrddhZ3CZksci8Kf9Qyk66sMfF +yteXG/8Rs/zMngVtxoAsM6TWOLAxru6QTBR7sGN2tbiV3p1yOxf33+Cwz/HIRlt9 +TAYro0LHmciFn9ivIUXz7il4 +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/etc/testCA/userCA/newcerts/0CAD.pem b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CAD.pem new file mode 100644 index 000000000000..7b9d8c4a57a8 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/newcerts/0CAD.pem @@ -0,0 +1,86 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3245 (0xcad) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Validity + Not Before: Jan 31 17:03:29 2011 GMT + Not After : Dec 26 17:03:29 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=Malverde + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:7b:c9:05:71:72:77:e4:7a:9b:3f:5f:5b:5d: + 2c:84:65:8a:3e:45:d5:29:71:cd:a0:01:46:f0:76: + a3:17:52:14:70:1c:68:11:b5:1e:de:4f:2a:c5:72: + d9:c5:dd:f3:e2:f3:5b:4b:c0:f1:92:29:3b:8a:1f: + 0a:56:39:fe:02:28:cc:32:76:81:d8:db:3e:21:62: + dd:89:24:07:0b:d9:3a:ae:19:78:61:a0:b3:d5:ed: + e0:00:02:87:cb:ff:79:e6:e9:b0:b7:38:a6:db:78: + e1:cc:60:dd:6b:d7:4a:97:80:81:76:3c:6d:91:e4: + de:09:cf:a0:e6:75:29:ba:c6:d1:9c:7d:90:5d:da: + af:e4:a0:9e:fa:7b:2a:9e:da:e4:d4:64:1e:4c:25: + 77:f0:3c:f5:ea:7a:8c:e8:9f:ed:ab:66:ee:c3:c7: + 72:8c:25:30:82:7e:3f:28:22:a1:b5:6a:1d:64:8f: + 8f:45:62:33:2e:f6:bb:0b:88:93:b7:0a:45:55:d6: + 2d:84:72:62:5d:26:ec:8b:db:58:4f:b8:23:48:ed: + be:f4:55:51:8c:5b:25:2e:cf:16:12:30:d4:05:68: + e4:78:db:ab:bb:66:89:19:70:d0:a9:09:48:44:fb: + f4:7a:6c:12:55:fd:14:13:da:1f:14:c4:de:83:12: + c6:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 27:3E:80:D5:75:E0:F6:D1:53:E0:68:92:6F:67:05:DB:36:08:95:21 + X509v3 Authority Key Identifier: + keyid:19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + + X509v3 CRL Distribution Points: + URI:http://localhost:8085/ca?issuer=CN=CAS Test User CA + + Signature Algorithm: sha1WithRSAEncryption + 51:8a:3e:fe:e5:b5:9a:f6:8d:82:f5:4c:0a:22:8f:5b:c9:64: + 07:c2:7a:3a:c6:6c:17:57:1b:da:f5:32:b7:83:cc:07:0e:90: + a6:ec:e0:3e:24:3a:70:51:1b:98:a0:d0:ac:32:00:f0:d5:1f: + bf:09:d9:32:9b:f1:c2:f4:9e:71:45:a8:f7:03:82:ce:e0:b9: + e6:e5:4b:85:b2:04:6d:7b:71:f2:28:3f:1a:48:b1:4c:0d:78: + 03:d4:67:15:d5:57:06:39:dd:17:a5:bd:49:10:42:c2:cc:c9: + 29:6f:96:b2:b0:00:c8:e7:5b:c2:dd:03:3d:7c:7f:06:5d:80: + f9:32:00:56:33:68:bc:9a:57:c5:ab:c4:11:29:5c:d6:e9:21: + 9b:25:30:d8:94:cd:5d:52:76:61:9c:cb:8c:dd:ee:5a:69:26: + a3:1f:8c:55:c0:10:e3:77:4c:82:79:a4:7c:c5:1e:b5:b6:d1: + 8c:6a:0e:33:ce:43:42:c1:0b:2e:7c:72:05:78:10:d7:7f:9c: + 5f:6d:16:be:fa:18:51:8a:5d:15:39:2f:a8:ed:39:51:73:1e: + e3:39:a9:d5:54:9a:fc:6e:37:5e:af:ef:86:af:38:8a:68:32: + 49:4a:b1:1e:91:0d:51:e4:c5:5e:3d:7a:4e:54:2b:e6:58:ae: + 63:a4:d7:82 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgICDK0wDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMzExNzAzMjlaFw0zMjEyMjYxNzAzMjlaMGcxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxETAPBgNVBAMTCE1hbHZlcmRlMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy3vJBXFyd+R6mz9fW10shGWKPkXV +KXHNoAFG8HajF1IUcBxoEbUe3k8qxXLZxd3z4vNbS8Dxkik7ih8KVjn+AijMMnaB +2Ns+IWLdiSQHC9k6rhl4YaCz1e3gAAKHy/955umwtzim23jhzGDda9dKl4CBdjxt +keTeCc+g5nUpusbRnH2QXdqv5KCe+nsqntrk1GQeTCV38Dz16nqM6J/tq2buw8dy +jCUwgn4/KCKhtWodZI+PRWIzLva7C4iTtwpFVdYthHJiXSbsi9tYT7gjSO2+9FVR +jFslLs8WEjDUBWjkeNuru2aJGXDQqQlIRPv0emwSVf0UE9ofFMTegxLGAQIDAQAB +o4HCMIG/MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJh +dGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQnPoDVdeD20VPgaJJvZwXbNgiVITAf +BgNVHSMEGDAWgBQZG3iNk2KySWaQVW5Qs9CvYes58zBEBgNVHR8EPTA7MDmgN6A1 +hjNodHRwOi8vbG9jYWxob3N0OjgwODUvY2E/aXNzdWVyPUNOPUNBUyBUZXN0IFVz +ZXIgQ0EwDQYJKoZIhvcNAQEFBQADggEBAFGKPv7ltZr2jYL1TAoij1vJZAfCejrG +bBdXG9r1MreDzAcOkKbs4D4kOnBRG5ig0KwyAPDVH78J2TKb8cL0nnFFqPcDgs7g +ueblS4WyBG17cfIoPxpIsUwNeAPUZxXVVwY53RelvUkQQsLMySlvlrKwAMjnW8Ld +Az18fwZdgPkyAFYzaLyaV8WrxBEpXNbpIZslMNiUzV1SdmGcy4zd7lppJqMfjFXA +EON3TIJ5pHzFHrW20YxqDjPOQ0LBCy58cgV4ENd/nF9tFr76GFGKXRU5L6jtOVFz +HuM5qdVUmvxuN16v74avOIpoMklKsR6RDVHkxV49ek5UK+ZYrmOk14I= +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/etc/testCA/userCA/private/cakey.pem b/cas-server-support-x509/etc/testCA/userCA/private/cakey.pem new file mode 100644 index 000000000000..6e4bc6bfd72a --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/private/cakey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAq505iAnW6EFlIbiMorfWbKnP9f75x+zjsbIWB40qjdi8exxz +hQmCyaKarwqzTtxTvZ8n0W2R5HeN/rx0NiZUCIHmH3TWWqRBfZWTXFsXHqM7kAiW +LWroAMGxPAoLky3EACfVre3RsmZELGCwL5Ime9SLJAHWYuJIFy48DRX830JZkDCE +C4GGKgGLWLoVA61uB1jPofcubshc8rGukDZn1DmWuAS0ZrD3Riq6IUNGufuT0TnW +/UC00p3p2E/crhSOcwUmilwhXDmlwJpmHLielFHucYyZqcIuOQCFBNEUpyMAT3tS +NWEK0gpkyTITfUZ9NfaxMUg4YGqbtb89IzWRIQIDAQABAoIBAQCR2DxccIBDQdBA +qCZc4v9HuckStm5widG34jD2mfHgOqFzvuot1bdgxN/Qgrd//Z7tN6UPRp8GGtSB ++IaKvmgMEtkPQqwovA8sO0HQ61jqoK6t6Z43WvNwMSv+aksL+Yjo2PGvrrzXcDHf +xQv2Ya5y7l6Raz5XKxc5HAHM3TLaSJODG/VNd2yrc/ILagHzgyGWKciUVitqonX5 +JfNQLkjrevGgIyjLYn/HDEkHXlnRId+MyIKO1pC9rcJd8jNKR1bX/VnNylWQl+Yz +A2tZAInGP8cdmJzbuek2M4fDF9lp0ajljATi2oYmnUpyMfDGu0IXWbtcZkYUJG2w +8/9Dl/sBAoGBAONi/x2V5v7+jlqk25X/Hhd5dbcejCdtYmP9cGXFE0qR+viOKKvL +BAk0iwqCeXfjjT4CvTJGg+0SuXu7iy843NvkpeTT/aOkpjphO9BkQ31m6VKcEUV4 +oqdxQ0+MbDSwUufWcPSYhE3rIrJTlrcOPQipgE0aO6LUqIg0bxy3ZHDVAoGBAME1 +lEaShU0hvVUJDIutrvARQSlvycAuRM1eJQ953aYTYWqiFC3A6NDBVXtQpxc0la1T +7cEROc3CsZzB/xB2MsPXdCJn4XRA4/2GJ82z1+JEh818pOfYLf3YJ2m6b8Zv9Zbu +nJfKKrmDiK0FB9JtzhD18OZODRZq1iUOlF9pbiUdAoGAIVbdgu/8q3yVULMFQyvh +tJ1pZg0DjbBbEcASH8nSd0eKHbFyQyYEFHlwvXy95JcnmWT7aoiuRS9OpNatpKHY +uCEBNdjjQoAco0ioZXid8KALK8KYQbBbYOOZc+tp3hvQPA/UBfjqxSXps23QFkzv +I/r+Rc/H2KsHftj5RaOaBXUCgYEAwP27AY9j9brI+0LRtEgmIQirNS/Dr+LHuPfc +Pww02aosOa5yR42VQMiNkWLrtKNDC2J1b8+8FIrwjSu+kvvuE2+IQsHPEyeqVNiH +s9J7xzk7CYgC1M88iX47SEIBWo8FGuF3q6s2aB46DNGkkY47MvfnzLSUIBtRkqxw +iqmuawUCgYEAqQSzIdPlKJ2Uq81ouOHHW6bV6rUEpy6n4mODQh8PVQnub5/kdRZ4 +ycnXYB/WxYHOL33kkOE8qUP1HsSJ0XMpMJ3FDzEk0tacWHYT8YexR5GWoQD2UffE +oKdBb934wlzKI+OLaHaU+tNViSQrtZFrC81r8IHiUNjOO2SmgkLdcCY= +-----END RSA PRIVATE KEY----- diff --git a/cas-server-support-x509/etc/testCA/userCA/private/cert.key b/cas-server-support-x509/etc/testCA/userCA/private/cert.key new file mode 100644 index 000000000000..0d5024467723 --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/private/cert.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAy3vJBXFyd+R6mz9fW10shGWKPkXVKXHNoAFG8HajF1IUcBxo +EbUe3k8qxXLZxd3z4vNbS8Dxkik7ih8KVjn+AijMMnaB2Ns+IWLdiSQHC9k6rhl4 +YaCz1e3gAAKHy/955umwtzim23jhzGDda9dKl4CBdjxtkeTeCc+g5nUpusbRnH2Q +Xdqv5KCe+nsqntrk1GQeTCV38Dz16nqM6J/tq2buw8dyjCUwgn4/KCKhtWodZI+P +RWIzLva7C4iTtwpFVdYthHJiXSbsi9tYT7gjSO2+9FVRjFslLs8WEjDUBWjkeNur +u2aJGXDQqQlIRPv0emwSVf0UE9ofFMTegxLGAQIDAQABAoIBAQCKAhWwBGavnB3v +54ZkMAh+b2PYooJVjqL4z4TumHg62+eStiF7SQ17l9QZgLDk+4lrNsSH13/AMwj3 +v9/B5ak6i7Ma0rd0fhZgjG5WkkHZLmcXJ503EciG4sQXHaA+orJqA7tgOSMqHY1/ +kX77olQr9vNXXtqtpb1WiCTHyVUZIIOCKIGbei7XUpu9JSO5ZeVEPVza2l4G8d6Q +nXuy+vH3+dSlxOvSOnK/w700mHT7byIqBmCKpZsNQ/tdggKvrWrmb90sRsVMMWL3 +65cu0zD25cY6KVsSnwmQ/LxR4YjZ0dVLGCGzqGjoUyQHWn4bIbw6IilYNTQDwI7J +NzALMgExAoGBAPD77jsc4dajUZmz1oM0AIrkse19JYHWW9/VHUcQdPNVNY8SWd3Z +3EWpuC8lSZHY6LmdYkLEq/wuJF/V3B2JnsTBAT9JGbOF5bw8vjRQDL1XxGJIIDie +vXCfRKW0O1ZP65akLHhhTG4Cu9XZwbvSKw904XK6TE6YkcRdLQBxoyEFAoGBANgp +qZ1Tv7sSqR7WbuzZiKCRpaPupAk84Ti3vq+IXJ5Xk4cf0jYsEOVOFBpLphZ9tex0 +I/KzsXcPYxYsr9Rroenx4dQCJ+LNbNadsSauIaPd3/OsWpKPsOIQzxnRG4/ya4qW +VJ+f0yKWx1a0OAHQ37Myn7kjM1SHPduM+sFJQBHNAoGAQ+ABsr6o0YbAphUkN2I/ +QN+3Sdf054QT7pCdJXX6Kst2QWz+yODjDPAHw6ex+BVrP1SX54xzkJ0Ce8fFrsAj +avGdw1B4n7r4ATPustdVL0N4OeJsyOXVH9KMSEviZqYuCgkFyYZGO7ojY9FootlI +bZsTIPheI8kPTYqc8UIQm20CgYBO9JjRsH9900kWzHhj5ukyzUBVaptcDHKvht4N +u58o7xAh8QuVYe8h8q0BFdSqrmw+3AUtZ1lRBbvJo7TtwAq6KvGzwL3U5/fzRFIq +sLv04NwmYzQKCJYxXLZo1apdn8yMKHDshTJXlf9luhoi/6pWCA2zUBo1q9h6Uzpc +U2clmQKBgQCoQwhZZuVAoeb54BejunwpUXHsIoLibs+b9f+4vs3okc9w+0qDVNEY +gg7Jn+Asj1q/4QQ2TRBUA9zLI/7AVFa+NK3Smbbl4wY27wbMEv31v6tfGXvUsRVU +b4h87rXw3u2AM1+VIcocNEuEsX/trpeFS1b94eFiTGL+BJKuAe9YDw== +-----END RSA PRIVATE KEY----- diff --git a/cas-server-support-x509/etc/testCA/userCA/serial b/cas-server-support-x509/etc/testCA/userCA/serial new file mode 100644 index 000000000000..3c2e26e6dfce --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/serial @@ -0,0 +1 @@ +0CAE diff --git a/cas-server-support-x509/etc/testCA/userCA/serial.old b/cas-server-support-x509/etc/testCA/userCA/serial.old new file mode 100644 index 000000000000..5785b5db1dcb --- /dev/null +++ b/cas-server-support-x509/etc/testCA/userCA/serial.old @@ -0,0 +1 @@ +0CAD diff --git a/cas-server-support-x509/pom.xml b/cas-server-support-x509/pom.xml new file mode 100644 index 000000000000..8e9cb29edc55 --- /dev/null +++ b/cas-server-support-x509/pom.xml @@ -0,0 +1,119 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-support-x509 + jar + Apereo CAS X.509 Client Certificate Support + + + + serac + Marvin S. Addison + marvin.addison@gmail.com + -5 + + developer + maintainer + + + + + + + cas-server-core + org.jasig.cas + ${project.version} + + + cas-server-support-generic + org.jasig.cas + ${project.version} + test + + + vt-crypt + edu.vt.middleware + 2.1.4 + + + org.springframework.webflow + spring-webflow + compile + + + net.sf.ehcache + ehcache + + + + org.springframework + spring-expression + + + + org.ldaptive + ldaptive + + + + org.jasig.cas + cas-server-support-legacy + ${project.version} + test + + + + org.jasig.cas + cas-server-support-ldap + ${project.version} + test-jar + test + + + + com.unboundid + unboundid-ldapsdk + test + + + + org.ldaptive + ldaptive-unboundid + ${ldaptive.version} + test + + + + org.hibernate + hibernate-validator + test + + + + + ${project.parent.basedir} + + diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/AbstractCRLRevocationChecker.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/AbstractCRLRevocationChecker.java new file mode 100644 index 000000000000..e528fb32cea0 --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/AbstractCRLRevocationChecker.java @@ -0,0 +1,177 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import java.security.GeneralSecurityException; +import java.security.cert.X509CRL; +import java.security.cert.X509CRLEntry; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import javax.validation.constraints.NotNull; + +import org.jasig.cas.adaptors.x509.util.CertUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Base class for all CRL-based revocation checkers. + * + * @author Marvin S. Addison + * @since 3.4.6 + * + */ +public abstract class AbstractCRLRevocationChecker implements RevocationChecker { + /** Logger instance. **/ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + /** + * Flag to indicate whether all + * crls should be checked for the cert resource. + * Defaults to false. + **/ + protected boolean checkAll; + + /** Policy to apply when CRL data is unavailable. */ + @NotNull + private RevocationPolicy unavailableCRLPolicy = new DenyRevocationPolicy(); + + /** Policy to apply when CRL data has expired. */ + @NotNull + private RevocationPolicy expiredCRLPolicy = new ThresholdExpiredCRLRevocationPolicy(); + + /** + * {@inheritDoc} + **/ + @Override + public void check(final X509Certificate cert) throws GeneralSecurityException { + if (cert == null) { + throw new IllegalArgumentException("Certificate cannot be null."); + } + logger.debug("Evaluating certificate revocation status for {}", CertUtils.toString(cert)); + final Collection crls = getCRLs(cert); + + if (crls == null || crls.isEmpty()) { + logger.warn("CRL data is not available for {}", CertUtils.toString(cert)); + this.unavailableCRLPolicy.apply(null); + return; + } + + final List expiredCrls = new ArrayList<>(); + final List revokedCrls = new ArrayList<>(); + + final Iterator it = crls.iterator(); + while (it.hasNext()) { + final X509CRL crl = it.next(); + if (CertUtils.isExpired(crl)) { + logger.warn("CRL data expired on {}", crl.getNextUpdate()); + expiredCrls.add(crl); + } + } + + if (crls.size() == expiredCrls.size()) { + logger.warn("All CRLs retrieved have expired. Applying CRL expiration policy..."); + for (final X509CRL crl : expiredCrls) { + this.expiredCRLPolicy.apply(crl); + } + } else { + crls.removeAll(expiredCrls); + logger.debug("Valid CRLs [{}] found that are not expired yet", crls); + + for (final X509CRL crl : crls) { + final X509CRLEntry entry = crl.getRevokedCertificate(cert); + if (entry != null) { + revokedCrls.add(entry); + } + } + + if (revokedCrls.size() == crls.size()) { + final X509CRLEntry entry = revokedCrls.get(0); + logger.warn("All CRL entries have been revoked. Rejecting the first entry [{}]", entry); + throw new RevokedCertificateException(entry); + } + } + } + + /** + * Sets the policy to apply when CRL data is unavailable. + * + * @param policy Revocation policy. + */ + public void setUnavailableCRLPolicy(final RevocationPolicy policy) { + this.unavailableCRLPolicy = policy; + } + + /** + * Sets the policy to apply when CRL data is expired. + * + * @param policy Revocation policy. + */ + public void setExpiredCRLPolicy(final RevocationPolicy policy) { + this.expiredCRLPolicy = policy; + } + + + /** + * Indicates whether all resources should be checked, + * or revocation should stop at the first resource + * that produces the cert. + * + * @param checkAll the check all + */ + public final void setCheckAll(final boolean checkAll) { + this.checkAll = checkAll; + } + + /** + * Gets the first fetched CRL for the given certificate. + * + * @param cert Certificate for which the CRL of the issuing CA should be retrieved. + * + * @return CRL for given cert, or null + */ + public final X509CRL getCRL(final X509Certificate cert) { + final Collection list = getCRLs(cert); + if (list != null && !list.isEmpty()) { + return list.iterator().next(); + } + logger.debug("No CRL could be found for {}", CertUtils.toString(cert)); + return null; + } + + /** + * Records the addition of a new CRL entry. + * @param id the id of the entry to keep track of + * @param crl new CRL entry + * @return true if the entry was added successfully. + * @since 4.1 + */ + protected abstract boolean addCRL(final Object id, final X509CRL crl); + + /** + * Gets the collection of CRLs for the given certificate. + * + * @param cert Certificate for which the CRL of the issuing CA should be retrieved. + * @return CRLs for given cert. + */ + protected abstract Collection getCRLs(final X509Certificate cert); +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/AllowRevocationPolicy.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/AllowRevocationPolicy.java new file mode 100644 index 000000000000..be13c4d90111 --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/AllowRevocationPolicy.java @@ -0,0 +1,51 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import java.security.GeneralSecurityException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Implements an unqualified allow policy. + * + * @author Marvin S. Addison + * @since 3.4.6 + * + */ +public final class AllowRevocationPolicy implements RevocationPolicy { + private final Logger logger = LoggerFactory.getLogger(getClass()); + + + /** + * Policy application does nothing to implement unqualfied allow. + * + * @param data SHOULD be null; ignored in all cases. + * + * @throws GeneralSecurityException Never thrown. + * + * @see org.jasig.cas.adaptors.x509.authentication.handler.support.RevocationPolicy#apply(java.lang.Object) + */ + @Override + public void apply(final Void data) throws GeneralSecurityException { + logger.info("Continuing since AllowRevocationPolicy is in effect."); + } +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/CRLDistributionPointRevocationChecker.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/CRLDistributionPointRevocationChecker.java new file mode 100644 index 000000000000..e91977dc76ea --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/CRLDistributionPointRevocationChecker.java @@ -0,0 +1,243 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.URLDecoder; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.jasig.cas.adaptors.x509.util.CertUtils; +import org.springframework.core.io.ByteArrayResource; + +import edu.vt.middleware.crypt.x509.ExtensionReader; +import edu.vt.middleware.crypt.x509.types.DistributionPoint; +import edu.vt.middleware.crypt.x509.types.DistributionPointList; +import edu.vt.middleware.crypt.x509.types.GeneralName; +import edu.vt.middleware.crypt.x509.types.GeneralNameList; + +import net.sf.ehcache.Cache; +import net.sf.ehcache.Element; + +import javax.validation.constraints.NotNull; + +/** + * Performs CRL-based revocation checking by consulting resources defined in + * the CRLDistributionPoints extension field on the certificate. Although RFC + * 2459 allows the distribution point name to have arbitrary meaning, this class + * expects the name to define an absolute URL, which is the most common + * implementation. This implementation caches CRL resources fetched from remote + * URLs to improve performance by avoiding CRL fetching on every revocation + * check. + * + * @author Marvin S. Addison + * @since 3.4.6 + * + */ +public class CRLDistributionPointRevocationChecker extends AbstractCRLRevocationChecker { + + /** CRL cache. */ + private final Cache crlCache; + + /** The CRL fetcher instance. **/ + private final CRLFetcher fetcher; + + private boolean throwOnFetchFailure; + + /** + * Creates a new instance that uses the given cache instance for CRL caching. + * + * @param crlCache Cache for CRL data. + */ + public CRLDistributionPointRevocationChecker(@NotNull final Cache crlCache) { + this(crlCache, new ResourceCRLFetcher()); + } + + /** + * Creates a new instance that uses the given cache instance for CRL caching. + * + * @param crlCache Cache for CRL data. + * @param throwOnFetchFailure the throw on fetch failure + */ + public CRLDistributionPointRevocationChecker(@NotNull final Cache crlCache, + final boolean throwOnFetchFailure) { + this(crlCache, new ResourceCRLFetcher()); + setThrowOnFetchFailure(throwOnFetchFailure); + } + + /** + * Instantiates a new CRL distribution point revocation checker. + * + * @param crlCache the crl cache + * @param fetcher the fetcher + */ + public CRLDistributionPointRevocationChecker(@NotNull final Cache crlCache, + @NotNull final CRLFetcher fetcher) { + this.crlCache = crlCache; + this.fetcher = fetcher; + } + + + /** + * Throws exceptions if fetching crl fails. Defaults to false. + * + * @param throwOnFetchFailure the throw on fetch failure + */ + public void setThrowOnFetchFailure(final boolean throwOnFetchFailure) { + this.throwOnFetchFailure = throwOnFetchFailure; + } + + /** + * {@inheritDoc} + * @see AbstractCRLRevocationChecker#getCRL(X509Certificate) + */ + @Override + protected List getCRLs(final X509Certificate cert) { + final URI[] urls = getDistributionPoints(cert); + logger.debug("Distribution points for {}: {}.", CertUtils.toString(cert), Arrays.asList(urls)); + final List listOfLocations = new ArrayList<>(urls.length); + boolean stopFetching = false; + + try { + for (int index = 0; !stopFetching && index < urls.length; index++) { + final URI url = urls[index]; + final Element item = this.crlCache.get(url); + + if (item != null) { + logger.debug("Found CRL in cache for {}", CertUtils.toString(cert)); + final byte[] encodedCrl = (byte[]) item.getObjectValue(); + final X509CRL crlFetched = this.fetcher.fetch(new ByteArrayResource(encodedCrl)); + + if (crlFetched != null) { + listOfLocations.add(crlFetched); + } else { + logger.warn("Could fetch X509 CRL for {}. Returned value is null", url); + } + } else { + logger.debug("CRL for {} is not cached. Fetching and caching...", CertUtils.toString(cert)); + try { + final X509CRL crl = this.fetcher.fetch(url); + if (crl != null) { + logger.info("Success. Caching fetched CRL at {}.", url); + addCRL(url, crl); + listOfLocations.add(crl); + } + } catch (final Exception e) { + logger.error("Error fetching CRL at {}", url, e); + if (this.throwOnFetchFailure) { + throw new RuntimeException(e); + } + } + } + + if (!this.checkAll && !listOfLocations.isEmpty()) { + logger.debug("CRL fetching is configured to not check all locations."); + stopFetching = true; + } + } + } catch (final Exception e) { + throw new RuntimeException(e); + } + + logger.debug("Found {} CRLs", listOfLocations.size()); + return listOfLocations; + } + + @Override + protected boolean addCRL(final Object id, final X509CRL crl) { + try { + if (crl == null) { + logger.debug("No CRL was passed. Removing {} from cache...", id); + return this.crlCache.remove(id); + } + + this.crlCache.put(new Element(id, crl.getEncoded())); + return this.crlCache.get(id) != null; + + } catch (final Exception e) { + logger.warn("Failed to add the crl entry [{}] to the cache", crl); + throw new RuntimeException(e); + } + } + + + /** + * Gets the distribution points. + * + * @param cert the cert + * @return the url distribution points + */ + private URI[] getDistributionPoints(final X509Certificate cert) { + final DistributionPointList points; + try { + points = new ExtensionReader(cert).readCRLDistributionPoints(); + } catch (final Exception e) { + logger.error("Error reading CRLDistributionPoints extension field on {}", CertUtils.toString(cert), e); + return new URI[0]; + } + + final List urls = new ArrayList<>(); + for (final DistributionPoint point : points.getItems()) { + final Object location = point.getDistributionPoint(); + if (location instanceof String) { + addURL(urls, (String) location); + } else if (location instanceof GeneralNameList) { + for (final GeneralName gn : ((GeneralNameList) location).getItems()) { + addURL(urls, gn.getName()); + } + } else { + logger.warn("{} not supported. String or GeneralNameList expected.", location); + } + } + + return urls.toArray(new URI[urls.size()]); + } + + /** + * Adds the url to the list. + * Build URI by components to facilitate proper encoding of querystring. + * e.g. http://example.com:8085/ca?action=crl&issuer=CN=CAS Test User CA + * + *

If uriString is encoded, it will be decoded with UTF-8 + * first before it's added to the list.

+ * @param list the list + * @param uriString the uri string + */ + private void addURL(final List list, final String uriString) { + try { + URI uri = null; + try { + final URL url = new URL(URLDecoder.decode(uriString, "UTF-8")); + uri = new URI(url.getProtocol(), url.getAuthority(), url.getPath(), url.getQuery(), null); + } catch (final MalformedURLException e) { + uri = new URI(uriString); + } + list.add(uri); + } catch (final Exception e) { + logger.warn("{} is not a valid distribution point URI.", uriString); + } + } + +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/CRLFetcher.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/CRLFetcher.java new file mode 100644 index 000000000000..f61307238b2f --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/CRLFetcher.java @@ -0,0 +1,50 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.security.cert.X509CRL; +import java.util.Set; + +/** + * Defines operations needed to a fetch a CRL. + * @author Misagh Moayyed + * @since 4.1 + */ +public interface CRLFetcher { + /** + * Fetches a collection of crls from the specified resources + * and returns a map of CRLs each tracked by its url. + * @param crls resources to retrieve + * @return map of crl entries and their urls + * @throws Exception the exception thrown if resources cant be fetched + */ + Set fetch(@NotNull @Size(min=1) Set crls) throws Exception; + + /** + * Fetches a single of crl from the specified resource + * and returns it. + * @param crl resources to retrieve + * @return the CRL entry + * @throws Exception the exception thrown if resources cant be fetched + */ + X509CRL fetch(@NotNull Object crl) throws Exception; +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/DenyRevocationPolicy.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/DenyRevocationPolicy.java new file mode 100644 index 000000000000..973cb0d319d0 --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/DenyRevocationPolicy.java @@ -0,0 +1,48 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import java.security.GeneralSecurityException; + + +/** + * Implements a deny policy by throwing an exception. + * + * @author Marvin S. Addison + * @since 3.4.6 + * + */ +public final class DenyRevocationPolicy implements RevocationPolicy { + + /** + * Policy application throws GeneralSecurityException to stop execution of + * whatever process invoked application of this policy. + * + * @param nothing SHOULD be null; ignored in all cases. + * + * @throws GeneralSecurityException Thrown in all cases. + * + * @see org.jasig.cas.adaptors.x509.authentication.handler.support.RevocationPolicy#apply(java.lang.Object) + */ + @Override + public void apply(final Void nothing) throws GeneralSecurityException { + throw new GeneralSecurityException("Aborting since DenyRevocationPolicy is in effect."); + } + +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ExpiredCRLException.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ExpiredCRLException.java new file mode 100644 index 000000000000..949810b51e47 --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ExpiredCRLException.java @@ -0,0 +1,106 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import org.joda.time.DateTime; + +import java.security.GeneralSecurityException; +import java.util.Date; + +/** + * Exception describing an expired CRL condition. + * + * @author Marvin S. Addison + * @since 3.4.6 + * + */ +public class ExpiredCRLException extends GeneralSecurityException { + /** Serialization version marker. */ + private static final long serialVersionUID = 5157864033250359972L; + + /** Identifier/name of CRL. */ + private final String id; + + /** CRL expiration date. */ + private final DateTime expirationDate; + + /** Leniency of expiration. */ + private final int leniency; + + /** + * Creates a new instance describing a CRL that expired on the given date. + * + * @param identifier Identifier or name that describes CRL. + * @param expirationDate CRL expiration date. + */ + public ExpiredCRLException(final String identifier, final Date expirationDate) { + this(identifier, expirationDate, 0); + } + + /** + * Creates a new instance describing a CRL that expired on a date that is + * more than leniency seconds beyond its expiration date. + * + * @param identifier Identifier or name that describes CRL. + * @param expirationDate CRL expiration date. + * @param leniency Number of seconds beyond the expiration date at which + * the CRL is considered expired. MUST be non-negative integer. + */ + public ExpiredCRLException(final String identifier, final Date expirationDate, final int leniency) { + this.id = identifier; + this.expirationDate = new DateTime(expirationDate); + if (leniency < 0) { + throw new IllegalArgumentException("Leniency cannot be negative."); + } + this.leniency = leniency; + } + + /** + * @return Returns the id. + */ + public String getId() { + return this.id; + } + + /** + * @return Returns the expirationDate. + */ + public DateTime getExpirationDate() { + return this.expirationDate == null ? null : new DateTime(this.expirationDate); + } + + /** + * @return Returns the leniency. + */ + public int getLeniency() { + return this.leniency; + } + + /** + * {@inheritDoc} + */ + @Override + public String getMessage() { + if (this.leniency > 0) { + return String.format("CRL %s expired on %s and is beyond the leniency period of %s seconds.", + this.id, this.expirationDate, this.leniency); + } + return String.format("CRL %s expired on %s", this.id, this.expirationDate); + } +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/NoOpRevocationChecker.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/NoOpRevocationChecker.java new file mode 100644 index 000000000000..e6f3a1456319 --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/NoOpRevocationChecker.java @@ -0,0 +1,47 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import java.security.GeneralSecurityException; +import java.security.cert.X509Certificate; + + +/** + * NO-OP implementation certificate revocation checker. + * + * @author Marvin S. Addison + * @since 3.4.6 + */ +public final class NoOpRevocationChecker implements RevocationChecker { + + /** + * NO-OP check implementation. + * + * @param certificate Certificate to check. + * + * @throws GeneralSecurityException Never thrown. + * + * @see org.jasig.cas.adaptors.x509.authentication.handler.support.RevocationChecker#check(java.security.cert.X509Certificate) + */ + @Override + public void check(final X509Certificate certificate) throws GeneralSecurityException { + // NO-OP + } + +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ResourceCRLFetcher.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ResourceCRLFetcher.java new file mode 100644 index 000000000000..1756f1106b89 --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ResourceCRLFetcher.java @@ -0,0 +1,105 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import org.jasig.cas.adaptors.x509.util.CertUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.AbstractResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; + +import javax.validation.constraints.NotNull; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.security.cert.X509CRL; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Handles the fetching of CRL objects based on resources. + * Supports http/ldap resources. + * @author Misagh Moayyed + * @since 4.1 + */ +public class ResourceCRLFetcher implements CRLFetcher { + /** Logger instance. */ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + /** + * Creates a new instance using the specified resources for CRL data. + */ + public ResourceCRLFetcher() {} + + @Override + public final Set fetch(final Set crls) throws Exception { + final Set results = new HashSet<>(); + for (final Object r : crls) { + logger.debug("Fetching CRL data from {}", r); + final X509CRL crl = fetchInternal(r); + if (crl != null) { + results.add(crl); + } + } + return results; + } + + @Override + public X509CRL fetch(final Object crl) throws Exception { + final Set results = fetch(Collections.singleton(crl)); + if (results.size() > 0) { + return results.iterator().next(); + } + logger.warn("Unable to fetch {}", crl); + return null; + } + + /** + * Fetch the resource. Designed so that extensions + * can decide how the resource should be retrieved. + * + * @param r the resource which can be {@link URL}, {@link URI}, {@link String} + * or {@link AbstractResource} + * @return the x 509 cRL + * @throws Exception the exception + */ + protected X509CRL fetchInternal(@NotNull final Object r) throws Exception { + Resource rs = null; + if (r instanceof URI) { + rs = new UrlResource(((URI) r).toURL()); + } else if (r instanceof URL) { + rs = new UrlResource(((URL) r)); + } else if (r instanceof AbstractResource) { + rs = (AbstractResource) r; + } else if (r instanceof String) { + rs = new UrlResource(new URL(r.toString())); + } + + if (rs == null) { + throw new IllegalArgumentException("Resource " + r + " could not be identified"); + } + + try (final InputStream ins = rs.getInputStream()) { + return (X509CRL) CertUtils.getCertificateFactory().generateCRL(ins); + } + } +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ResourceCRLRevocationChecker.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ResourceCRLRevocationChecker.java new file mode 100644 index 000000000000..45afabe8eb16 --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ResourceCRLRevocationChecker.java @@ -0,0 +1,189 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import com.google.common.collect.ImmutableSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.Resource; + +import javax.security.auth.x500.X500Principal; +import javax.validation.constraints.Min; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * CRL-based revocation checker that uses one or more CRL resources to fetch + * local or remote CRL data periodically. CRL resources should be supplied for + * the issuers of all certificates (and intervening certificates for certificate + * chains) that are expected to be presented to {@link X509CredentialsAuthenticationHandler}. + * + * @author Marvin S. Addison + * @since 3.4.7 + * + */ +public class ResourceCRLRevocationChecker extends AbstractCRLRevocationChecker + implements InitializingBean { + + /** Default refresh interval is 1 hour. */ + public static final int DEFAULT_REFRESH_INTERVAL = 3600; + + /** Executor responsible for refreshing CRL data. */ + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + /** CRL refresh interval in seconds. */ + private int refreshInterval = DEFAULT_REFRESH_INTERVAL; + + /** Handles fetching CRL data. */ + private final CRLFetcher fetcher; + + /** Map of CRL issuer to CRL. */ + private final Map crlIssuerMap = + Collections.synchronizedMap(new HashMap()); + + /** Resource CRLs. **/ + private final Set resources; + + /** + * Creates a new instance using the specified resource for CRL data. + * + * @param crl Resource containing CRL data. MUST NOT be null. + */ + public ResourceCRLRevocationChecker(final Resource crl) { + this(new Resource[] {crl}); + } + + /** + * Creates a new instance using the specified resources for CRL data. + * + * @param crls Resources containing CRL data. MUST NOT be null and MUST have + * at least one non-null element. + */ + public ResourceCRLRevocationChecker(final Resource[] crls) { + this(new ResourceCRLFetcher(), crls); + } + + /** + * Instantiates a new Resource cRL revocation checker. + * + * @param fetcher the fetcher + * @param crls the crls + * @since 4.1 + */ + public ResourceCRLRevocationChecker(final CRLFetcher fetcher, final Resource[] crls) { + this.fetcher = fetcher; + this.resources = ImmutableSet.copyOf(crls); + } + + + /** + * Sets the interval at which CRL data should be reloaded from CRL resources. + * + * @param seconds Refresh interval in seconds; MUST be positive integer. + */ + public void setRefreshInterval(@Min(1) final int seconds) { + this.refreshInterval = seconds; + } + + + /** + * {@inheritDoc} + * Initializes the process that periodically fetches CRL data. */ + @Override + public void afterPropertiesSet() throws Exception { + try { + // Fetch CRL data synchronously and throw exception to abort if any fail + final Set results = this.fetcher.fetch(getResources()); + ResourceCRLRevocationChecker.this.addCrls(results); + } catch (final Exception e) { + throw new RuntimeException(e); + } + + // Set up the scheduler to fetch periodically to implement refresh + final Runnable scheduledFetcher = new Runnable() { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public void run() { + try { + final Set resources = ResourceCRLRevocationChecker.this.getResources(); + final Set results = getFetcher().fetch(resources); + ResourceCRLRevocationChecker.this.addCrls(results); + } catch (final Exception e) { + logger.debug(e.getMessage(), e); + } + } + }; + this.scheduler.scheduleAtFixedRate( + scheduledFetcher, this.refreshInterval, this.refreshInterval, TimeUnit.SECONDS); + } + + /** + * Add fetched crls to the map. + * + * @param results the results + */ + private void addCrls(final Set results) { + final Iterator it = results.iterator(); + while (it.hasNext()) { + final X509CRL entry = it.next(); + addCRL(entry.getIssuerX500Principal(), entry); + } + } + + /** + * @return Returns the CRL fetcher component. + */ + protected CRLFetcher getFetcher() { + return this.fetcher; + } + + protected Set getResources() { + return this.resources; + } + + @Override + protected boolean addCRL(final Object issuer, final X509CRL crl) { + logger.debug("Adding CRL for issuer {}", issuer); + this.crlIssuerMap.put((X500Principal) issuer, crl); + return this.crlIssuerMap.containsKey(issuer); + } + + @Override + protected Collection getCRLs(final X509Certificate cert) { + return Collections.singleton(this.crlIssuerMap.get(cert.getIssuerX500Principal())); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + this.scheduler.shutdown(); + } + +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/RevocationChecker.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/RevocationChecker.java new file mode 100644 index 000000000000..77df7f395568 --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/RevocationChecker.java @@ -0,0 +1,42 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import java.security.GeneralSecurityException; +import java.security.cert.X509Certificate; + + +/** + * Strategy interface for checking revocation status of a certificate. + * + * @author Marvin S. Addison + * @since 3.4.6 + * + */ +public interface RevocationChecker { + /** + * Checks the revocation status of the given certificate. + * + * @param certificate Certificate to examine. + * + * @throws GeneralSecurityException If certificate has been revoked or the revocation + * check fails for some reason such as revocation data not available. + */ + void check(X509Certificate certificate) throws GeneralSecurityException; +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/RevocationPolicy.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/RevocationPolicy.java new file mode 100644 index 000000000000..f3b4914da164 --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/RevocationPolicy.java @@ -0,0 +1,42 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import java.security.GeneralSecurityException; + + +/** + * Strategy interface for enforcing various policy matters related to certificate + * revocation, such as what to do when revocation data is unavailable or stale. + * + * @author Marvin S. Addison + * @since 3.4.6 + * + */ +public interface RevocationPolicy { + /** + * Applies the policy. + * + * @param data Data to help make a decision according to policy. + * + * @throws GeneralSecurityException When policy application poses a security + * risk or policy application is prevented for security reasons. + */ + void apply(T data) throws GeneralSecurityException; +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/RevokedCertificateException.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/RevokedCertificateException.java new file mode 100644 index 000000000000..3d36670ae480 --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/RevokedCertificateException.java @@ -0,0 +1,199 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.cert.X509CRLEntry; +import java.util.Date; + + +/** + * Exception that describes a revoked X.509 certificate. + * + * @author Marvin S. Addison + * @since 3.4.6 + * + */ +public final class RevokedCertificateException extends GeneralSecurityException { + + /** OID for reasonCode CRL extension. */ + public static final String CRL_REASON_OID = "2.5.29.21"; + + /** The Constant serialVersionUID. */ + private static final long serialVersionUID = 8827788431199129708L; + + /** The logger. */ + private static final Logger LOGGER = LoggerFactory.getLogger(RevokedCertificateException.class); + + /** CRL revocation reason codes per RFC 3280. */ + public enum Reason { + + /** The Unspecified. */ + Unspecified, + + /** The Key compromise. */ + KeyCompromise, + + /** The CA compromise. */ + CACompromise, + + /** The Affiliation changed. */ + AffiliationChanged, + + /** The Superseded. */ + Superseded, + + /** The Cessation of operation. */ + CessationOfOperation, + + /** The Certificate hold. */ + CertificateHold, + + /** The Remove from crl. */ + RemoveFromCRL, + + /** The Privilege withdrawn. */ + PrivilegeWithdrawn, + + /** The AA compromise. */ + AACompromise; + + /** + * Convert code to reason. + * + * @param code the code + * @return the reason + */ + public static Reason fromCode(final int code) { + final Reason[] reasons = Reason.values(); + + for (int i = 0; i < reasons.length; i++) { + if (i == code) { + return reasons[i]; + } + } + throw new IllegalArgumentException("Unknown CRL reason code."); + } + } + + /** The revocation date. */ + private final DateTime revocationDate; + + /** The serial. */ + private final BigInteger serial; + + /** The reason. */ + private final Reason reason; + + /** + * Instantiates a new revoked certificate exception. + * + * @param revoked the revoked + * @param serial the serial + */ + public RevokedCertificateException(final Date revoked, final BigInteger serial) { + this(revoked, serial, null); + } + + /** + * Instantiates a new revoked certificate exception. + * + * @param revoked the revoked + * @param serial the serial + * @param reason the reason + */ + public RevokedCertificateException(final Date revoked, final BigInteger serial, final Reason reason) { + this.revocationDate = new DateTime(revoked); + this.serial = serial; + this.reason = reason; + } + + /** + * Instantiates a new revoked certificate exception. + * + * @param entry the entry + */ + public RevokedCertificateException(final X509CRLEntry entry) { + this(entry.getRevocationDate(), entry.getSerialNumber(), getReasonFromX509Entry(entry)); + } + + /** + * Get reason from the x509 entry. + * @param entry the entry + * @return reason or null + */ + private static Reason getReasonFromX509Entry(final X509CRLEntry entry) { + if (entry.hasExtensions()) { + try { + final int code = Integer.parseInt( + new String(entry.getExtensionValue(CRL_REASON_OID), "ASCII")); + if (code < Reason.values().length) { + return Reason.fromCode(code); + } + } catch (final Exception e) { + LOGGER.trace("An exception occurred when resolving extension value: {}", e.getMessage()); + } + } + return null; + } + + /** + * Gets the revocation date. + * + * @return Returns the revocationDate. + */ + public DateTime getRevocationDate() { + return this.revocationDate == null ? null : new DateTime(this.revocationDate); + } + + /** + * Gets the serial. + * + * @return Returns the serial. + */ + public BigInteger getSerial() { + return this.serial; + } + + /** + * Gets the reason. + * + * @return Returns the reason. + */ + public Reason getReason() { + return this.reason; + } + + /** + * {@inheritDoc} + */ + @Override + public String getMessage() { + if (this.reason != null) { + return String.format("Certificate %s revoked on %s for reason %s", + this.serial, this.revocationDate, this.reason); + } + return String.format("Certificate %s revoked on %s", this.serial, this.revocationDate); + } +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ThresholdExpiredCRLRevocationPolicy.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ThresholdExpiredCRLRevocationPolicy.java new file mode 100644 index 000000000000..109ac9af0706 --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ThresholdExpiredCRLRevocationPolicy.java @@ -0,0 +1,84 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import org.jasig.cas.adaptors.x509.util.CertUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.validation.constraints.Min; +import java.security.GeneralSecurityException; +import java.security.cert.X509CRL; +import java.util.Calendar; + + +/** + * Implements a policy to handle expired CRL data whereby expired data is permitted + * up to a threshold period of time but not afterward. + * + * @author Marvin S. Addison + * @since 3.4.6 + * + */ +public final class ThresholdExpiredCRLRevocationPolicy implements RevocationPolicy { + /** Default threshold is 48 hours. */ + private static final int DEFAULT_THRESHOLD = 172800; + + /** Logger instance. */ + private final Logger logger = LoggerFactory.getLogger(getClass()); + + + /** Expired threshold period in seconds. */ + @Min(0) + private int threshold = DEFAULT_THRESHOLD; + + + /** + * {@inheritDoc} + * The CRL next update time is compared against the current time with the threshold + * applied and rejected if and only if the next update time is in the past. + * + * @param crl CRL instance to evaluate. + * + * @throws GeneralSecurityException On expired CRL data. Check the exception type for exact details + * + * @see org.jasig.cas.adaptors.x509.authentication.handler.support.RevocationPolicy#apply(java.lang.Object) + */ + @Override + public void apply(final X509CRL crl) throws GeneralSecurityException { + final Calendar cutoff = Calendar.getInstance(); + if (CertUtils.isExpired(crl, cutoff.getTime())) { + cutoff.add(Calendar.SECOND, -this.threshold); + if (CertUtils.isExpired(crl, cutoff.getTime())) { + throw new ExpiredCRLException(crl.toString(), cutoff.getTime(), this.threshold); + } + logger.info(String.format("CRL expired on %s but is within threshold period, %s seconds.", + crl.getNextUpdate(), this.threshold)); + } + } + + /** + * Sets the threshold period of time after which expired CRL data is rejected. + * + * @param threshold Number of seconds; MUST be non-negative integer. + */ + public void setThreshold(final int threshold) { + this.threshold = threshold; + } +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/X509CredentialsAuthenticationHandler.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/X509CredentialsAuthenticationHandler.java new file mode 100644 index 000000000000..28d615da489b --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/X509CredentialsAuthenticationHandler.java @@ -0,0 +1,319 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import java.security.GeneralSecurityException; +import java.security.Principal; +import java.security.cert.X509Certificate; +import java.util.Set; +import java.util.regex.Pattern; + +import org.jasig.cas.adaptors.x509.authentication.principal.X509CertificateCredential; +import org.jasig.cas.adaptors.x509.util.CertUtils; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.PreventedException; +import org.jasig.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler; +import org.jasig.cas.authentication.Credential; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.security.auth.login.FailedLoginException; +import javax.validation.constraints.NotNull; + +/** + * Authentication Handler that accepts X509 Certificates, determines their + * validity and ensures that they were issued by a trusted issuer. (targeted at + * X509v3) Optionally checks KeyUsage extension in the user certificate + * (container should do that too). Note that this handler trusts the servlet + * container to do some initial checks like path validation. Deployers can + * supply an optional pattern to match subject dns against to further restrict + * certificates in case they are not using their own issuer. It's also possible + * to specify a maximum pathLength for the SUPPLIED certificates. (note that + * this does not include a pathLength check for the root certificate) + * [PathLength is 0 for the CA certificate that issues the end-user certificate] + * + * @author Scott Battaglia + * @author Jan Van der Velpen + * @since 3.0.0.4 + */ +public class X509CredentialsAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler { + + /** Default setting to limit the number of intermediate certificates. */ + private static final int DEFAULT_MAXPATHLENGTH = 1; + + /** Default setting whether to allow unspecified number of intermediate certificates. */ + private static final boolean DEFAULT_MAXPATHLENGTH_ALLOW_UNSPECIFIED = false; + + /** Default setting to check keyUsage extension. */ + private static final boolean DEFAULT_CHECK_KEYUSAGE = false; + + /** + * Default setting to force require "KeyUsage" extension. + */ + private static final boolean DEFAULT_REQUIRE_KEYUSAGE = false; + + /** Default subject pattern match. */ + private static final Pattern DEFAULT_SUBJECT_DN_PATTERN = Pattern.compile(".*"); + + /** OID for KeyUsage X.509v3 extension field. */ + private static final String KEY_USAGE_OID = "2.5.29.15"; + + /** Instance of Logging. */ + private final Logger logger = LoggerFactory.getLogger(getClass()); + + /** The compiled pattern supplied by the deployer. */ + @NotNull + private Pattern regExTrustedIssuerDnPattern; + + /** + * Deployer supplied setting for maximum pathLength in a SUPPLIED + * certificate. + */ + private int maxPathLength = DEFAULT_MAXPATHLENGTH; + + /** + * Deployer supplied setting to allow unlimited pathLength in a SUPPLIED + * certificate. + */ + private boolean maxPathLengthAllowUnspecified = DEFAULT_MAXPATHLENGTH_ALLOW_UNSPECIFIED; + + /** Deployer supplied setting to check the KeyUsage extension. */ + private boolean checkKeyUsage = DEFAULT_CHECK_KEYUSAGE; + + /** + * Deployer supplied setting to force require the correct KeyUsage + * extension. + */ + private boolean requireKeyUsage = DEFAULT_REQUIRE_KEYUSAGE; + + /** The compiled pattern for trusted DN's supplied by the deployer. */ + @NotNull + private Pattern regExSubjectDnPattern = DEFAULT_SUBJECT_DN_PATTERN; + + /** Certificate revocation checker component. */ + @NotNull + private RevocationChecker revocationChecker = new NoOpRevocationChecker(); + + + @Override + public boolean supports(final Credential credential) { + return credential != null + && X509CertificateCredential.class.isAssignableFrom(credential + .getClass()); + } + + /** + * {@inheritDoc} + */ + @Override + protected final HandlerResult doAuthentication(final Credential credential) throws GeneralSecurityException, PreventedException { + + final X509CertificateCredential x509Credential = (X509CertificateCredential) credential; + final X509Certificate[] certificates = x509Credential.getCertificates(); + + X509Certificate clientCert = null; + boolean hasTrustedIssuer = false; + for (int i = certificates.length - 1; i >= 0; i--) { + final X509Certificate certificate = certificates[i]; + logger.debug("Evaluating {}", CertUtils.toString(certificate)); + + validate(certificate); + + if (!hasTrustedIssuer) { + hasTrustedIssuer = isCertificateFromTrustedIssuer(certificate); + } + + // getBasicConstraints returns pathLenContraint which is generally + // >=0 when this is a CA cert and -1 when it's not + final int pathLength = certificate.getBasicConstraints(); + if (pathLength < 0) { + logger.debug("Found valid client certificate"); + clientCert = certificate; + } else { + logger.debug("Found valid CA certificate"); + } + } + if (hasTrustedIssuer && clientCert != null) { + x509Credential.setCertificate(clientCert); + return new DefaultHandlerResult(this, x509Credential, this.principalFactory.createPrincipal(x509Credential.getId())); + } + throw new FailedLoginException(); + } + + public void setTrustedIssuerDnPattern(final String trustedIssuerDnPattern) { + this.regExTrustedIssuerDnPattern = Pattern.compile(trustedIssuerDnPattern); + } + + /** + * @param maxPathLength The maxPathLength to set. + */ + public void setMaxPathLength(final int maxPathLength) { + this.maxPathLength = maxPathLength; + } + + /** + * @param allowed Allow CA certs to have unlimited intermediate certs (default=false). + */ + public void setMaxPathLengthAllowUnspecified(final boolean allowed) { + this.maxPathLengthAllowUnspecified = allowed; + } + + /** + * @param checkKeyUsage The checkKeyUsage to set. + */ + public void setCheckKeyUsage(final boolean checkKeyUsage) { + this.checkKeyUsage = checkKeyUsage; + } + + /** + * @param requireKeyUsage The requireKeyUsage to set. + */ + public void setRequireKeyUsage(final boolean requireKeyUsage) { + this.requireKeyUsage = requireKeyUsage; + } + + public void setSubjectDnPattern(final String subjectDnPattern) { + this.regExSubjectDnPattern = Pattern.compile(subjectDnPattern); + } + + /** + * Sets the component responsible for evaluating certificate revocation status for client + * certificates presented to handler. The default checker is a NO-OP implementation + * for backward compatibility with previous versions that do not perform revocation + * checking. + * + * @param checker Revocation checker component. + */ + public void setRevocationChecker(final RevocationChecker checker) { + this.revocationChecker = checker; + } + + /** + * Validate the X509Certificate received. + * + * @param cert the cert + * @throws GeneralSecurityException the general security exception + */ + private void validate(final X509Certificate cert) throws GeneralSecurityException { + cert.checkValidity(); + this.revocationChecker.check(cert); + + final int pathLength = cert.getBasicConstraints(); + if (pathLength < 0) { + if (!isCertificateAllowed(cert)) { + throw new FailedLoginException( + "Certificate subject does not match pattern " + this.regExSubjectDnPattern.pattern()); + } + if (this.checkKeyUsage && !isValidKeyUsage(cert)) { + throw new FailedLoginException( + "Certificate keyUsage constraint forbids SSL client authentication."); + } + } else { + // Check pathLength for CA cert + if (pathLength == Integer.MAX_VALUE && !this.maxPathLengthAllowUnspecified) { + throw new FailedLoginException("Unlimited certificate path length not allowed by configuration."); + } else if (pathLength > this.maxPathLength && pathLength < Integer.MAX_VALUE) { + throw new FailedLoginException(String.format( + "Certificate path length %s exceeds maximum value %s.", pathLength, this.maxPathLength)); + } + } + } + + /** + * Checks if is valid key usage.

+ * KeyUsage ::= BIT STRING { digitalSignature (0), nonRepudiation (1), + * keyEncipherment (2), dataEncipherment (3), keyAgreement (4), + * keyCertSign (5), cRLSign (6), encipherOnly (7), decipherOnly (8) } + * + * @param certificate the certificate + * @return true, if valid key usage + */ + private boolean isValidKeyUsage(final X509Certificate certificate) { + logger.debug("Checking certificate keyUsage extension"); + final boolean[] keyUsage = certificate.getKeyUsage(); + if (keyUsage == null) { + logger.warn("Configuration specifies checkKeyUsage but keyUsage extension not found in certificate."); + return !this.requireKeyUsage; + } + + final boolean valid; + if (isCritical(certificate, KEY_USAGE_OID) || this.requireKeyUsage) { + logger.debug("KeyUsage extension is marked critical or required by configuration."); + valid = keyUsage[0]; + } else { + logger.debug( + "KeyUsage digitalSignature=%s, Returning true since keyUsage validation not required by configuration."); + valid = true; + } + return valid; + } + + /** + * Checks if critical extension oids contain the extension oid. + * + * @param certificate the certificate + * @param extensionOid the extension oid + * @return true, if critical + */ + private boolean isCritical(final X509Certificate certificate, final String extensionOid) { + final Set criticalOids = certificate.getCriticalExtensionOIDs(); + + if (criticalOids == null || criticalOids.isEmpty()) { + return false; + } + + return criticalOids.contains(extensionOid); + } + + /** + * Checks if is certificate allowed based no the pattern given. + * + * @param cert the cert + * @return true, if certificate allowed + */ + private boolean isCertificateAllowed(final X509Certificate cert) { + return doesNameMatchPattern(cert.getSubjectDN(), this.regExSubjectDnPattern); + } + + /** + * Checks if is certificate from trusted issuer based on the regex pattern. + * + * @param cert the cert + * @return true, if certificate from trusted issuer + */ + private boolean isCertificateFromTrustedIssuer(final X509Certificate cert) { + return doesNameMatchPattern(cert.getIssuerDN(), this.regExTrustedIssuerDnPattern); + } + + /** + * Does principal name match pattern? + * + * @param principal the principal + * @param pattern the pattern + * @return true, if successful + */ + private boolean doesNameMatchPattern(final Principal principal, + final Pattern pattern) { + final String name = principal.getName(); + final boolean result = pattern.matcher(name).matches(); + logger.debug(String.format("%s matches %s == %s", pattern.pattern(), name, result)); + return result; + } +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ldap/LdaptiveResourceCRLFetcher.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ldap/LdaptiveResourceCRLFetcher.java new file mode 100644 index 000000000000..479e8ac0a1cc --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ldap/LdaptiveResourceCRLFetcher.java @@ -0,0 +1,153 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.adaptors.x509.authentication.handler.support.ldap; + +import java.security.cert.CertificateException; +import java.security.cert.X509CRL; +import javax.validation.constraints.NotNull; +import org.jasig.cas.adaptors.x509.authentication.handler.support.ResourceCRLFetcher; +import org.jasig.cas.util.CompressionUtils; +import org.ldaptive.ConnectionConfig; +import org.ldaptive.ConnectionFactory; +import org.ldaptive.DefaultConnectionFactory; +import org.ldaptive.LdapAttribute; +import org.ldaptive.LdapEntry; +import org.ldaptive.LdapException; +import org.ldaptive.Response; +import org.ldaptive.ResultCode; +import org.ldaptive.SearchExecutor; +import org.ldaptive.SearchResult; +import org.springframework.core.io.ByteArrayResource; + +/** + * Fetches a CRL from an LDAP instance. + * @author Daniel Fisher + * @since 4.1 + */ +public class LdaptiveResourceCRLFetcher extends ResourceCRLFetcher { + + /** Search exec that looks for the attribute. */ + protected final SearchExecutor searchExecutor; + + /** The connection config to prep for connections. **/ + protected final ConnectionConfig connectionConfig; + + /** + * Instantiates a new Ldap resource cRL fetcher. + + * @param connectionConfig the connection configuration + * @param searchExecutor the search executor + */ + public LdaptiveResourceCRLFetcher( + @NotNull final ConnectionConfig connectionConfig, + @NotNull final SearchExecutor searchExecutor) { + this.connectionConfig = connectionConfig; + this.searchExecutor = searchExecutor; + } + + @Override + protected X509CRL fetchInternal(final Object r) throws Exception { + if (r.toString().toLowerCase().startsWith("ldap")) { + return fetchCRLFromLdap(r); + } + return super.fetchInternal(r); + } + + /** + * Downloads a CRL from given LDAP url. + * + * @param r the resource that is the ldap url. + * @return the x 509 cRL + * @throws Exception if connection to ldap fails, or attribute to get the revocation list is unavailable + */ + protected X509CRL fetchCRLFromLdap(final Object r) throws Exception { + try { + final String ldapURL = r.toString(); + logger.debug("Fetching CRL from ldap {}", ldapURL); + + final Response result = performLdapSearch(ldapURL); + if (result.getResultCode() == ResultCode.SUCCESS) { + final LdapEntry entry = result.getResult().getEntry(); + final LdapAttribute attribute = entry.getAttribute(); + + logger.debug("Located entry [{}]. Retrieving first attribute [{}]", + entry, attribute); + return fetchX509CRLFromAttribute(attribute); + } else { + logger.debug("Failed to execute the search [{}]", result); + } + + throw new CertificateException("Failed to establish a connection ldap and search."); + + } catch (final LdapException e) { + logger.error(e.getMessage(), e); + throw new CertificateException(e); + } + } + + + /** + * Gets x509 cRL from attribute. Retrieves the binary attribute value, + * decodes it to base64, and fetches it as a byte-array resource. + * + * @param aval the attribute, which may be null if it's not found + * @return the x 509 cRL from attribute + * @throws Exception if attribute not found or could could not be decoded. + */ + protected X509CRL fetchX509CRLFromAttribute(final LdapAttribute aval) throws Exception { + if (aval != null) { + final byte[] val = aval.getBinaryValue(); + if (val == null || val.length == 0) { + throw new CertificateException("Empty attribute. Can not download CRL from ldap"); + } + final byte[] decoded64 = CompressionUtils.decodeBase64ToByteArray(val); + if (decoded64 == null) { + throw new CertificateException("Could not decode the attribute value to base64"); + } + logger.debug("Retrieved CRL from ldap as byte array decoded in base64. Fetching..."); + return super.fetch(new ByteArrayResource(decoded64)); + } + throw new CertificateException("Attribute not found. Can not retrieve CRL"); + } + + /** + * Executes an LDAP search against the supplied URL. + * + * @param ldapURL to search + * @return search result + * @throws LdapException if an error occurs performing the search + */ + protected Response performLdapSearch(final String ldapURL) throws LdapException { + final ConnectionFactory connectionFactory = prepareConnectionFactory(ldapURL); + return this.searchExecutor.search(connectionFactory); + } + + /** + * Prepare a new LDAP connection. + * + * @param ldapURL the ldap uRL + * @return connection factory + */ + protected ConnectionFactory prepareConnectionFactory(final String ldapURL) { + final ConnectionConfig cc = ConnectionConfig.newConnectionConfig(this.connectionConfig); + cc.setLdapUrl(ldapURL); + return new DefaultConnectionFactory(cc); + } +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ldap/PoolingLdaptiveResourceCRLFetcher.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ldap/PoolingLdaptiveResourceCRLFetcher.java new file mode 100644 index 000000000000..c2d2167aaa74 --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ldap/PoolingLdaptiveResourceCRLFetcher.java @@ -0,0 +1,127 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.adaptors.x509.authentication.handler.support.ldap; + +import java.util.HashMap; +import java.util.Map; +import javax.annotation.PreDestroy; +import javax.validation.constraints.NotNull; +import org.ldaptive.ConnectionConfig; +import org.ldaptive.ConnectionFactory; +import org.ldaptive.DefaultConnectionFactory; +import org.ldaptive.SearchExecutor; +import org.ldaptive.pool.BlockingConnectionPool; +import org.ldaptive.pool.ConnectionPool; +import org.ldaptive.pool.PoolConfig; +import org.ldaptive.pool.PooledConnectionFactory; + +/** + * Fetches a CRL from an LDAP instance. + * @author Daniel Fisher + * @since 4.1 + */ +public class PoolingLdaptiveResourceCRLFetcher extends LdaptiveResourceCRLFetcher { + + /** Connection pool template. */ + protected final BlockingConnectionPool connectionPool; + + /** Map of connection pools. */ + private final Map connectionPoolMap = new HashMap<>(); + + /** + * Instantiates a new Ldap resource cRL fetcher. + + * @param connectionConfig the connection configuration + * @param searchExecutor the search executor + * @param connectionPool pooling configuration + */ + public PoolingLdaptiveResourceCRLFetcher( + @NotNull final ConnectionConfig connectionConfig, + @NotNull final SearchExecutor searchExecutor, + @NotNull final BlockingConnectionPool connectionPool) { + super(connectionConfig, searchExecutor); + this.connectionPool = connectionPool; + } + + /** + * Close connection pull and shut down the executor. + */ + @PreDestroy + public void destroy() { + logger.debug("Shutting down connection pools..."); + for (final PooledConnectionFactory factory : connectionPoolMap.values()) { + factory.getConnectionPool().close(); + } + } + + @Override + protected ConnectionFactory prepareConnectionFactory(final String ldapURL) { + final PooledConnectionFactory connectionFactory; + synchronized (connectionPoolMap) { + if (connectionPoolMap.containsKey(ldapURL)) { + connectionFactory = connectionPoolMap.get(ldapURL); + } else { + connectionFactory = new PooledConnectionFactory(newConnectionPool(ldapURL)); + connectionPoolMap.put(ldapURL, connectionFactory); + } + } + return connectionFactory; + } + + + /** + * Creates a new instance of a connection pool. Copied from {@link #connectionPool}. + * + * @param ldapURL to connect to + * @return connection pool + */ + private ConnectionPool newConnectionPool(final String ldapURL) { + final BlockingConnectionPool pool = new BlockingConnectionPool( + newPoolConfig(this.connectionPool.getPoolConfig()), + (DefaultConnectionFactory) super.prepareConnectionFactory(ldapURL)); + pool.setBlockWaitTime(this.connectionPool.getBlockWaitTime()); + pool.setActivator(this.connectionPool.getActivator()); + pool.setPassivator(this.connectionPool.getPassivator()); + pool.setValidator(this.connectionPool.getValidator()); + pool.setConnectOnCreate(this.connectionPool.getConnectOnCreate()); + pool.setFailFastInitialize(this.connectionPool.getFailFastInitialize()); + pool.setName(String.format("x509-crl-%s", ldapURL)); + pool.setPruneStrategy(this.connectionPool.getPruneStrategy()); + pool.initialize(); + return pool; + } + + /** + * Creates a new instance of pool config. + * + * @param config to copy properties from + * @return pool config + */ + private PoolConfig newPoolConfig(final PoolConfig config) { + final PoolConfig pc = new PoolConfig(); + pc.setMinPoolSize(config.getMinPoolSize()); + pc.setMaxPoolSize(config.getMaxPoolSize()); + pc.setValidateOnCheckIn(config.isValidateOnCheckIn()); + pc.setValidateOnCheckOut(config.isValidateOnCheckOut()); + pc.setValidatePeriodically(config.isValidatePeriodically()); + pc.setValidatePeriod(config.getValidatePeriod()); + return pc; + } +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/AbstractX509PrincipalResolver.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/AbstractX509PrincipalResolver.java new file mode 100644 index 000000000000..6776a20fde9e --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/AbstractX509PrincipalResolver.java @@ -0,0 +1,51 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.principal; + +import java.security.cert.X509Certificate; + +import org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver; +import org.jasig.cas.authentication.Credential; + +/** + * Abstract class in support of multiple resolvers for X509 Certificates. + * + * @author Scott Battaglia + * @since 3.0.0.4 + */ +public abstract class AbstractX509PrincipalResolver extends PersonDirectoryPrincipalResolver { + + @Override + protected String extractPrincipalId(final Credential credential) { + return resolvePrincipalInternal(((X509CertificateCredential) credential).getCertificate()); + } + + @Override + public boolean supports(final Credential credential) { + return credential instanceof X509CertificateCredential; + } + + /** + * Resolve principal internally, and return the id. + * + * @param certificate the certificate + * @return the string + */ + protected abstract String resolvePrincipalInternal(final X509Certificate certificate); +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509CertificateCredential.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509CertificateCredential.java new file mode 100644 index 000000000000..a668a3229904 --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509CertificateCredential.java @@ -0,0 +1,82 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.principal; + +import com.google.common.collect.ImmutableList; +import org.jasig.cas.adaptors.x509.util.CertUtils; +import org.jasig.cas.authentication.AbstractCredential; + +import javax.validation.constraints.NotNull; +import java.security.cert.X509Certificate; + +/** + * An X.509 certificate credential. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.0.0.4 + * + */ +public final class X509CertificateCredential extends AbstractCredential { + + /** Unique Id for serialization. */ + private static final long serialVersionUID = 631753409512746474L; + + /** The collection of certificates sent with the request. */ + private final X509Certificate[] certificates; + + /** The certificate that we actually use. */ + private X509Certificate certificate; + + /** + * Instantiates a new x509 certificate credential. + * + * @param certificates the certificates + */ + public X509CertificateCredential(@NotNull final X509Certificate[] certificates) { + this.certificates = ImmutableList.copyOf(certificates).toArray(new X509Certificate[certificates.length]); + } + + public X509Certificate[] getCertificates() { + return ImmutableList.copyOf(this.certificates).toArray(new X509Certificate[this.certificates.length]); + } + + public void setCertificate(final X509Certificate certificate) { + this.certificate = certificate; + } + + public X509Certificate getCertificate() { + return this.certificate; + } + + @Override + public String getId() { + X509Certificate cert = null; + if (this.certificate != null) { + cert = this.certificate; + } else if (this.certificates.length > 0) { + cert = this.certificates[0]; + } + + if (cert != null) { + return CertUtils.toString(cert); + } + return UNKNOWN_ID; + } +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SerialNumberAndIssuerDNPrincipalResolver.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SerialNumberAndIssuerDNPrincipalResolver.java new file mode 100644 index 000000000000..00ca1b0ab0d1 --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SerialNumberAndIssuerDNPrincipalResolver.java @@ -0,0 +1,77 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.principal; + +import javax.validation.constraints.NotNull; +import java.security.cert.X509Certificate; + +/** + * This class is targeted at usage for mapping to an existing user record. It + * can construct a highly-likely unique DN based on a certificate's serialnumber + * and its issuerDN. example: + * SERIALNUMBER=20267647332258882251479793556682961758, SERIALNUMBER=200301, + * CN=Citizen CA, C=BE see RFC3280 The combination of a certificate serial + * number and the issuerDN *should* be unique: - The certificate serialNumber is + * by its nature unique for a certain issuer. - The issuerDN is RECOMMENDED to + * be unique. Both the serial number and the issuerDN are REQUIRED in a + * certificate. Note: comparison rules state the compare should be + * case-insensitive. LDAP value description: EQUALITY distinguishedNameMatch + * SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 [=distinguishedName] + * + * @author Jan Van der Velpen + * @since 3.1 + */ +public final class X509SerialNumberAndIssuerDNPrincipalResolver extends AbstractX509PrincipalResolver { + + /** Prefix for Certificate Serial Number. */ + @NotNull + private String serialNumberPrefix = "SERIALNUMBER="; + + /** Prefix for Value Delimiter. */ + @NotNull + private String valueDelimiter = ", "; + + /** + * Sets a prefix for the certificate serialnumber (default: "SERIALNUMBER="). + * + * @param serialNumberPrefix The serialNumberPrefix to set. + */ + public void setSerialNumberPrefix(final String serialNumberPrefix) { + this.serialNumberPrefix = serialNumberPrefix; + } + + /** + * Sets a delimiter to separate the two certificate properties in the string. + * (default: ", ") + * + * @param valueDelimiter The valueDelimiter to set. + */ + public void setValueDelimiter(final String valueDelimiter) { + this.valueDelimiter = valueDelimiter; + } + + @Override + protected String resolvePrincipalInternal(final X509Certificate certificate) { + final StringBuilder builder = new StringBuilder(this.serialNumberPrefix); + builder.append(certificate.getSerialNumber().toString()); + builder.append(this.valueDelimiter); + builder.append(certificate.getIssuerDN().getName()); + return builder.toString(); + } +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SerialNumberPrincipalResolver.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SerialNumberPrincipalResolver.java new file mode 100644 index 000000000000..c228e7e2b4eb --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SerialNumberPrincipalResolver.java @@ -0,0 +1,36 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.principal; + +import java.security.cert.X509Certificate; + +/** + * Returns a new principal based on the Sereial Number of the certificate. + * + * @author Scott Battaglia + * @since 3.0.0.4 + */ +public final class X509SerialNumberPrincipalResolver extends AbstractX509PrincipalResolver { + + @Override + protected String resolvePrincipalInternal( + final X509Certificate certificate) { + return certificate.getSerialNumber().toString(); + } +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectAlternativeNameUPNPrincipalResolver.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectAlternativeNameUPNPrincipalResolver.java new file mode 100644 index 000000000000..718e20d2e4c1 --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectAlternativeNameUPNPrincipalResolver.java @@ -0,0 +1,152 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.principal; + +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.ASN1TaggedObject; +import org.bouncycastle.asn1.DERObject; +import org.bouncycastle.asn1.DERObjectIdentifier; +import org.bouncycastle.asn1.DERUTF8String; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.List; + +/** + * Credential to principal resolver that extracts Subject Alternative Name UPN extension + * from the provided certificate if available as a resolved principal id. + * + * @author Dmitriy Kopylenko + * @since 4.1.0 + */ +public class X509SubjectAlternativeNameUPNPrincipalResolver extends AbstractX509PrincipalResolver { + + /** + * ObjectID for upn altName for windows smart card logon. + */ + public static final String UPN_OBJECTID = "1.3.6.1.4.1.311.20.2.3"; + + /** + * Retrieves Subject Alternative Name UPN extension as a principal id String. + * + * @param certificate X.509 certificate credential. + * + * @return Resolved principal ID or null if no SAN UPN extension is available in provided certificate. + * + * @see AbstractX509PrincipalResolver#resolvePrincipalInternal(java.security.cert.X509Certificate) + * @see java.security.cert.X509Certificate#getSubjectAlternativeNames() + */ + @Override + protected String resolvePrincipalInternal(final X509Certificate certificate) { + logger.debug("Resolving principal from Subject Alternative Name UPN for {}", certificate); + try { + final Collection> subjectAltNames = certificate.getSubjectAlternativeNames(); + if (subjectAltNames != null) { + for (final List sanItem : subjectAltNames) { + final ASN1Sequence seq = getAltnameSequence(sanItem); + final String upnString = getUPNStringFromSequence(seq); + if (upnString != null) { + return upnString; + } + } + } + } catch (final CertificateParsingException e) { + logger.error("Error is encountered while trying to retrieve subject alternative names collection from certificate", e); + logger.debug("Returning null principal id..."); + return null; + } + logger.debug("Returning null principal id..."); + return null; + } + + /** + * Get UPN String. + * + * @param seq ASN1Sequence abstraction representing subject alternative name. + * First element is the object identifier, second is the object itself. + * + * @return UPN string or null + */ + private String getUPNStringFromSequence(final ASN1Sequence seq) { + if (seq != null) { + // First in sequence is the object identifier, that we must check + final DERObjectIdentifier id = DERObjectIdentifier.getInstance(seq.getObjectAt(0)); + if (id != null && UPN_OBJECTID.equals(id.getId())) { + final ASN1TaggedObject obj = (ASN1TaggedObject) seq.getObjectAt(1); + final DERUTF8String str = DERUTF8String.getInstance(obj.getObject()); + return str.getString(); + } + } + return null; + } + + /** + * Get alt name seq. + * + * @param sanItem subject alternative name value encoded as a two elements List with elem(0) representing object id and elem(1) + * representing object (subject alternative name) itself. + * + * @return ASN1Sequence abstraction representing subject alternative name or null if the passed in + * List doesn't contain at least to elements + * as expected to be returned by implementation of {@code X509Certificate.html#getSubjectAlternativeNames} + * + * @see + * X509Certificate#getSubjectAlternativeNames + */ + private ASN1Sequence getAltnameSequence(final List sanItem) { + //Should not be the case, but still, a extra "safety" check + if (sanItem.size() < 2) { + logger.error("Subject Alternative Name List does not contain at least two required elements. Returning null principal id..."); + } + final Integer itemType = (Integer) sanItem.get(0); + if (itemType == 0) { + final byte[] altName = (byte[]) sanItem.get(1); + return getAltnameSequence(altName); + } + return null; + } + + /** + * Get alt name seq. + * + * @param sanValue subject alternative name value encoded as byte[] + * + * @return ASN1Sequence abstraction representing subject alternative name + * + * @see + * X509Certificate#getSubjectAlternativeNames + */ + private ASN1Sequence getAltnameSequence(final byte[] sanValue) { + DERObject oct = null; + try (final ByteArrayInputStream bInput = new ByteArrayInputStream(sanValue)) { + try (final ASN1InputStream input = new ASN1InputStream(bInput)) { + oct = input.readObject(); + } catch (final IOException e) { + logger.error("Error on getting Alt Name as a DERSEquence: {}", e.getMessage(), e); + } + return ASN1Sequence.getInstance(oct); + } catch (final IOException e) { + logger.error("An error has occurred while reading the subject alternative name value", e); + } + return null; + } +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectDNPrincipalResolver.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectDNPrincipalResolver.java new file mode 100644 index 000000000000..70e5ce8cf46c --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectDNPrincipalResolver.java @@ -0,0 +1,36 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.principal; + +import java.security.cert.X509Certificate; + +/** + * Returns a principal based on the Subject DNs name. + * + * @author Scott Battaglia + * @since 3.0.0.4 + */ +public final class X509SubjectDNPrincipalResolver extends AbstractX509PrincipalResolver { + + @Override + protected String resolvePrincipalInternal( + final X509Certificate certificate) { + return certificate.getSubjectDN().getName(); + } +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectPrincipalResolver.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectPrincipalResolver.java new file mode 100644 index 000000000000..685e5ed1e0ec --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectPrincipalResolver.java @@ -0,0 +1,156 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.principal; + +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.validation.constraints.NotNull; + +import edu.vt.middleware.crypt.x509.DNUtils; +import edu.vt.middleware.crypt.x509.types.AttributeType; + + +/** + * Credential to principal resolver that extracts one or more attribute values + * from the certificate subject DN and combines them with intervening delimiters. + * + * @author Marvin S. Addison + * @since 3.4.4 + * + */ +public class X509SubjectPrincipalResolver extends AbstractX509PrincipalResolver { + + /** Pattern used to extract attribute names from descriptor. */ + private static final Pattern ATTR_PATTERN = Pattern.compile("\\$(\\w+)"); + + /** Descriptor representing an abstract format of the principal to be resolved.*/ + @NotNull + private String descriptor; + + /** + * Sets the descriptor that describes for format of the principal ID to + * create from X.509 subject DN attributes. The descriptor is made up of + * common X.509 attribute names prefixed by "$", which are replaced by + * attribute values extracted from DN attribute values. + *

+ * EXAMPLE: + *

+ * {@code + * + * <bean class="org.jasig.cas.adaptors.x509.authentication.principal.X509SubjectPrincipalResolver" + * p:descriptor="$UID@$DC.$DC" /> + * + * } + * + * The above bean when applied to a certificate with the DN + *

+ * DC=edu, DC=vt/UID=jacky, CN=Jascarnella Ellagwonto

+ *

+ * produces the principal jacky@vt.edu.

+ * + * @param s Descriptor string where attribute names are prefixed with "$" + * to identify replacement by real attribute values from the subject DN. + * Valid attributes include common X.509 DN attributes such as the following: + *
    + *
  • C
  • + *
  • CN
  • + *
  • DC
  • + *
  • EMAILADDRESS
  • + *
  • L
  • + *
  • O
  • + *
  • OU
  • + *
  • SERIALNUMBER
  • + *
  • ST
  • + *
  • UID
  • + *
  • UNIQUEIDENTIFIER
  • + *
+ * For a complete list of supported attributes, see + * {@link edu.vt.middleware.crypt.x509.types.AttributeType}. + * + */ + public void setDescriptor(final String s) { + this.descriptor = s; + } + + /** + * Replaces placeholders in the descriptor with values extracted from attribute + * values in relative distinguished name components of the DN. + * + * @param certificate X.509 certificate credential. + * @return Resolved principal ID. + * @see AbstractX509PrincipalResolver#resolvePrincipalInternal(java.security.cert.X509Certificate) + */ + @Override + protected String resolvePrincipalInternal(final X509Certificate certificate) { + logger.debug("Resolving principal for {}", certificate); + final StringBuffer sb = new StringBuffer(); + final Matcher m = ATTR_PATTERN.matcher(this.descriptor); + final Map attrMap = new HashMap<>(); + String name; + String[] values; + AttributeContext context; + while (m.find()) { + name = m.group(1); + if (!attrMap.containsKey(name)) { + values = DNUtils.getAttributeValues( + certificate.getSubjectX500Principal(), + AttributeType.fromName(name)); + attrMap.put(name, new AttributeContext(name, values)); + } + context = attrMap.get(name); + m.appendReplacement(sb, context.nextValue()); + } + m.appendTail(sb); + return sb.toString(); + } + + private static final class AttributeContext { + private int currentIndex; + private String name; + private final String[] values; + + /** + * Instantiates a new attribute context. + * + * @param name the name + * @param values the values + */ + public AttributeContext(final String name, final String[] values) { + this.values = values; + } + + + /** + * Retrieve the next value, by incrementing the current index. + * + * @return the string + * @throws IllegalStateException if no values are remaining. + */ + public String nextValue() { + if (this.currentIndex == this.values.length) { + throw new IllegalStateException("No values remaining for attribute " + this.name); + } + return this.values[this.currentIndex++]; + } + } +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/util/CertUtils.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/util/CertUtils.java new file mode 100644 index 000000000000..1b3b7570a6d6 --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/util/CertUtils.java @@ -0,0 +1,115 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.util; + +import java.io.InputStream; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.util.Date; + +import edu.vt.middleware.crypt.util.CryptReader; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.springframework.core.io.Resource; + + +/** + * Utility class with methods to support various operations on X.509 certs. + * + * @author Marvin S. Addison + * @since 3.4.6 + * + */ +public final class CertUtils { + + /** X509 certificate type. */ + public static final String X509_CERTIFICATE_TYPE = "X509"; + + + /** Suppressed constructor of utility class. */ + private CertUtils() { + } + + /** + * Determines whether the given CRL is expired by examining the nextUpdate field. + * + * @param crl CRL to examine. + * + * @return True if current system time is after CRL next update, false otherwise. + */ + public static boolean isExpired(final X509CRL crl) { + return isExpired(crl, new Date(System.currentTimeMillis())); + } + + /** + * Determines whether the given CRL is expired by comparing the nextUpdate field + * with a given date. + * + * @param crl CRL to examine. + * @param reference Reference date for comparison. + * + * @return True if reference date is after CRL next update, false otherwise. + */ + public static boolean isExpired(final X509CRL crl, final Date reference) { + return reference.after(crl.getNextUpdate()); + } + + /** + * Read certificate. + * + * @param resource the resource to read the cert from + * @return the x 509 certificate + */ + public static X509Certificate readCertificate(final Resource resource) { + try (final InputStream in = resource.getInputStream()) { + return (X509Certificate) CryptReader.readCertificate(in); + } catch (final Exception e) { + throw new RuntimeException("Error reading certificate " + resource, e); + } + } + + /** + * Creates a unique and human-readable representation of the given certificate. + * + * @param cert Certificate. + * + * @return String representation of a certificate that includes the subject and serial number. + */ + public static String toString(final X509Certificate cert) { + return new ToStringBuilder(cert, ToStringStyle.NO_CLASS_NAME_STYLE) + .append("subjectDn", cert.getSubjectDN()) + .append("serialNumber", cert.getSerialNumber()) + .build(); + } + + /** + * Gets a certificate factory for creating X.509 artifacts. + * + * @return X509 certificate factory. + */ + public static CertificateFactory getCertificateFactory() { + try { + return CertificateFactory.getInstance(X509_CERTIFICATE_TYPE); + } catch (final CertificateException e) { + throw new IllegalStateException("X509 certificate type not supported by default provider."); + } + } +} diff --git a/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/web/flow/X509CertificateCredentialsNonInteractiveAction.java b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/web/flow/X509CertificateCredentialsNonInteractiveAction.java new file mode 100644 index 000000000000..56d936b79fe8 --- /dev/null +++ b/cas-server-support-x509/src/main/java/org/jasig/cas/adaptors/x509/web/flow/X509CertificateCredentialsNonInteractiveAction.java @@ -0,0 +1,58 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.web.flow; + +import java.security.cert.X509Certificate; + +import org.jasig.cas.adaptors.x509.authentication.principal.X509CertificateCredential; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.web.flow.AbstractNonInteractiveCredentialsAction; +import org.springframework.webflow.execution.RequestContext; + +/** + * Concrete implementation of AbstractNonInteractiveCredentialsAction that + * obtains the X509 Certificates from the HttpServletRequest and places them in + * the X509CertificateCredential. + * + * @author Scott Battaglia + * @since 3.0.0.4 + */ +public final class X509CertificateCredentialsNonInteractiveAction extends AbstractNonInteractiveCredentialsAction { + + private static final String CERTIFICATE_REQUEST_ATTRIBUTE = "javax.servlet.request.X509Certificate"; + + @Override + protected Credential constructCredentialsFromRequest(final RequestContext context) { + final X509Certificate[] certificates = (X509Certificate[]) context + .getExternalContext().getRequestMap().get( + CERTIFICATE_REQUEST_ATTRIBUTE); + + if (certificates == null || certificates.length == 0) { + if (logger.isDebugEnabled()) { + logger.debug("Certificates not found in request."); + } + return null; + } + + if (logger.isDebugEnabled()) { + logger.debug("Certificate found in request."); + } + return new X509CertificateCredential(certificates); + } +} diff --git a/cas-server-support-x509/src/site/site.xml b/cas-server-support-x509/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-support-x509/src/site/site.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/cas-server-3.4.2/cas-server-support-trusted/src/test/clover/clover.license b/cas-server-support-x509/src/test/clover/clover.license similarity index 100% rename from cas-server-3.4.2/cas-server-support-trusted/src/test/clover/clover.license rename to cas-server-support-x509/src/test/clover/clover.license diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/AbstractCRLRevocationCheckerTests.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/AbstractCRLRevocationCheckerTests.java new file mode 100644 index 000000000000..264514942444 --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/AbstractCRLRevocationCheckerTests.java @@ -0,0 +1,92 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import java.security.GeneralSecurityException; +import java.security.cert.X509Certificate; + +import org.jasig.cas.adaptors.x509.util.CertUtils; +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; + +/** + * Base class for {@link RevocationChecker} unit tests. + * + * @author Marvin S. Addison + * @since 3.4.6 + * + */ +public abstract class AbstractCRLRevocationCheckerTests { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** Certificate to be tested. */ + private final X509Certificate[] certificates; + + /** Expected result of check; null for success. */ + private final GeneralSecurityException expected; + + /** + * Creates a new test instance with given parameters. + * + * @param certFiles File names of certificates to check. + * @param expected Expected result of check; null to indicate expected success. + */ + public AbstractCRLRevocationCheckerTests( + final String[] certFiles, + final GeneralSecurityException expected) { + + this.expected = expected; + this.certificates = new X509Certificate[certFiles.length]; + int i = 0; + for (final String file : certFiles) { + this.certificates[i++] = CertUtils.readCertificate(new ClassPathResource(file)); + } + } + + /** + * Test method for {@link AbstractCRLRevocationChecker#check(X509Certificate)}. + */ + @Test + public void checkCertificate() { + try { + for (final X509Certificate cert : this.certificates) { + getChecker().check(cert); + } + if (this.expected != null) { + Assert.fail("Expected exception of type " + this.expected.getClass()); + } + } catch (final GeneralSecurityException e) { + if (this.expected == null) { + Assert.fail("Revocation check failed unexpectedly with exception: " + e); + } else { + final Class expectedClass = this.expected.getClass(); + final Class actualClass = e.getClass(); + Assert.assertTrue( + String.format("Expected exception of type %s but got %s", expectedClass, actualClass), + expectedClass.isAssignableFrom(actualClass)); + } + } + } + + protected abstract RevocationChecker getChecker(); +} diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/AbstractX509LdapTests.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/AbstractX509LdapTests.java new file mode 100644 index 000000000000..b4ec01c8c09b --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/AbstractX509LdapTests.java @@ -0,0 +1,84 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import com.unboundid.ldap.sdk.LDAPConnection; +import org.apache.commons.io.IOUtils; +import org.jasig.cas.adaptors.ldap.AbstractLdapTests; +import org.jasig.cas.util.CompressionUtils; +import org.ldaptive.AttributeModification; +import org.ldaptive.AttributeModificationType; +import org.ldaptive.Connection; +import org.ldaptive.DefaultConnectionFactory; +import org.ldaptive.LdapAttribute; +import org.ldaptive.LdapEntry; +import org.ldaptive.ModifyOperation; +import org.ldaptive.ModifyRequest; +import org.springframework.core.io.ClassPathResource; + +import java.util.Collection; + +/** + * Parent class to help with testing x509 operations that deal with LDAP. + * @author Misagh Moayyed + * @since 4.1 + */ +public abstract class AbstractX509LdapTests extends AbstractLdapTests { + private static final String DN = "CN=x509,ou=people,dc=example,dc=org"; + + public static void bootstrap() throws Exception { + initDirectoryServer(); + getDirectory().populateEntries(new ClassPathResource("ldif/users-x509.ldif").getInputStream()); + + /** + * Dynamically set the attribute value to the crl content. + * Encode it as base64 first. Doing this in the code rather + * than in the ldif file to ensure the attribute can be populated + * without dependencies on the classpath and or filesystem. + */ + final Collection col = getDirectory().getLdapEntries(); + for (final LdapEntry ldapEntry : col) { + if (ldapEntry.getDn().equals(DN)) { + final LdapAttribute attr = new LdapAttribute(true); + + byte[] value = new byte[1024]; + IOUtils.read(new ClassPathResource("userCA-valid.crl").getInputStream(), value); + value = CompressionUtils.encodeBase64ToByteArray(value); + attr.setName("certificateRevocationList"); + attr.addBinaryValue(value); + + final LDAPConnection serverCon = getDirectory().getConnection(); + final String address = "ldap://" + serverCon.getConnectedAddress() + ":" + serverCon.getConnectedPort(); + final Connection conn = DefaultConnectionFactory.getConnection(address); + conn.open(); + final ModifyOperation modify = new ModifyOperation(conn); + modify.execute(new ModifyRequest(ldapEntry.getDn(), + new AttributeModification(AttributeModificationType.ADD, attr))); + conn.close(); + serverCon.close(); + return; + } + } + } + + public final String getTestDN() { + return DN; + } +} diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/CRLDistributionPointRevocationCheckerTests.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/CRLDistributionPointRevocationCheckerTests.java new file mode 100644 index 000000000000..eafc1cb92a47 --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/CRLDistributionPointRevocationCheckerTests.java @@ -0,0 +1,242 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import net.sf.ehcache.Cache; +import net.sf.ehcache.CacheManager; +import org.apache.commons.io.IOUtils; +import org.jasig.cas.adaptors.x509.util.MockWebServer; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.FileSystemResource; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.cert.X509CRL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + + +/** + * Unit test for {@link CRLDistributionPointRevocationChecker} class. + * + * @author Marvin S. Addison + * @since 3.4.76 + * + */ +@RunWith(Parameterized.class) +public class CRLDistributionPointRevocationCheckerTests extends AbstractCRLRevocationCheckerTests { + + /** Instance under test. */ + private final CRLDistributionPointRevocationChecker checker; + + /** Answers requests for CRLs made to localhost:8085. */ + private final MockWebServer webServer; + + + /** + * Creates a new test instance with given parameters. + * + * @param checker Revocation checker instance. + * @param expiredCRLPolicy Policy instance for handling expired CRL data. + * @param certFiles File names of certificates to check. + * @param crlFile File name of CRL file to serve out. + * @param expected Expected result of check; null to indicate expected success. + */ + public CRLDistributionPointRevocationCheckerTests( + final CRLDistributionPointRevocationChecker checker, + final RevocationPolicy expiredCRLPolicy, + final String[] certFiles, + final String crlFile, + final GeneralSecurityException expected) throws Exception { + + super(certFiles, expected); + + final File file = new File(System.getProperty("java.io.tmpdir"), "ca.crl"); + if (file.exists()) { + file.delete(); + } + final OutputStream out = new FileOutputStream(file); + IOUtils.copy(new ClassPathResource(crlFile).getInputStream(), out); + + this.checker = checker; + this.checker.setExpiredCRLPolicy(expiredCRLPolicy); + this.webServer = new MockWebServer(8085, new FileSystemResource(file), "text/plain"); + logger.debug("Web server listening on port 8085 serving file {}", crlFile); + } + + /** + * Gets the unit test parameters. + * + * @return Test parameter data. + */ + @Parameters + public static Collection getTestParameters() throws Exception { + CacheManager.getInstance().removeAllCaches(); + final Collection params = new ArrayList<>(); + Cache cache; + final ThresholdExpiredCRLRevocationPolicy defaultPolicy = new ThresholdExpiredCRLRevocationPolicy(); + final ThresholdExpiredCRLRevocationPolicy zeroThresholdPolicy = new ThresholdExpiredCRLRevocationPolicy(); + zeroThresholdPolicy.setThreshold(0); + + // Test case #0 + // Valid certificate on valid CRL data with encoded url + cache = new Cache("crlCache-0", 100, false, false, 20, 10); + CacheManager.getInstance().addCache(cache); + params.add(new Object[]{ + new CRLDistributionPointRevocationChecker(cache), + defaultPolicy, + new String[]{"uservalid-encoded-crl.crt"}, + "test ca.crl", + null, + }); + + // Test case #1 + // Valid certificate on valid CRL data + cache = new Cache("crlCache-1", 100, false, false, 20, 10); + CacheManager.getInstance().addCache(cache); + params.add(new Object[]{ + new CRLDistributionPointRevocationChecker(cache, true), + defaultPolicy, + new String[]{"user-valid-distcrl.crt"}, + "userCA-valid.crl", + null, + }); + + + // Test case #2 + // Revoked certificate on valid CRL data + cache = new Cache("crlCache-2", 100, false, false, 20, 10); + CacheManager.getInstance().addCache(cache); + params.add(new Object[] { + new CRLDistributionPointRevocationChecker(cache), + defaultPolicy, + new String[] {"user-revoked-distcrl.crt"}, + "userCA-valid.crl", + new RevokedCertificateException(new Date(), new BigInteger("1")), + }); + + // Test case #3 + // Valid certificate on expired CRL data + cache = new Cache("crlCache-3", 100, false, false, 20, 10); + CacheManager.getInstance().addCache(cache); + params.add(new Object[] { + new CRLDistributionPointRevocationChecker(cache), + zeroThresholdPolicy, + new String[] {"user-valid-distcrl.crt"}, + "userCA-expired.crl", + new ExpiredCRLException("test", new Date()), + }); + + // Test case #4 + // Valid certificate on expired CRL data with custom expiration + // policy to always allow expired CRL data + cache = new Cache("crlCache-4", 100, false, false, 20, 10); + CacheManager.getInstance().addCache(cache); + params.add(new Object[] { + new CRLDistributionPointRevocationChecker(cache), + new RevocationPolicy() { + @Override + public void apply(final X509CRL crl) {/* Do nothing to allow unconditionally */} + }, + new String[] {"user-valid-distcrl.crt"}, + "userCA-expired.crl", + null, + }); + + // Test case #5 + // Valid certificate with no CRL distribution points defined but with + // "AllowRevocationPolicy" set to allow unavailable CRL data + cache = new Cache("crlCache-5", 100, false, false, 20, 10); + CacheManager.getInstance().addCache(cache); + final CRLDistributionPointRevocationChecker checker5 = new CRLDistributionPointRevocationChecker(cache); + checker5.setUnavailableCRLPolicy(new AllowRevocationPolicy()); + params.add(new Object[] { + checker5, + defaultPolicy, + new String[] {"user-valid.crt"}, + "userCA-expired.crl", + null, + }); + + // Test case #6 + // EJBCA test case + // Revoked certificate with CRL distribution point URI that is technically + // not a valid URI since the issuer DN in the querystring is not encoded per + // the escaping of reserved characters in RFC 2396. + // Make sure we can convert given URI to valid URI and confirm it's revoked + cache = new Cache("crlCache-6", 100, false, false, 20, 10); + CacheManager.getInstance().addCache(cache); + params.add(new Object[] { + new CRLDistributionPointRevocationChecker(cache), + defaultPolicy, + new String[] {"user-revoked-distcrl2.crt"}, + "userCA-valid.crl", + new RevokedCertificateException(new Date(), new BigInteger("1")), + }); + + return params; + } + + /** + * Called once before every test. + * + * @throws Exception On setup errors. + */ + @Before + public void setUp() throws Exception { + this.webServer.start(); + Thread.sleep(500); + } + + /** + * Called once before every test. + * + * @throws Exception On setup errors. + */ + @After + public void tearDown() throws Exception { + logger.debug("Stopping web server..."); + this.webServer.stop(); + Thread.sleep(500); + logger.debug("Web server stopped [{}]", !this.webServer.isRunning()); + } + + @AfterClass + public static void destroy() { + final File file = new File("ca.crl"); + if (file.exists()) { + file.delete(); + } + } + + @Override + protected RevocationChecker getChecker() { + return this.checker; + } +} diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ResourceCRLRevocationCheckerTests.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ResourceCRLRevocationCheckerTests.java new file mode 100644 index 000000000000..874ebb4f1de7 --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ResourceCRLRevocationCheckerTests.java @@ -0,0 +1,155 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.cert.X509CRL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.springframework.core.io.ClassPathResource; + + +/** + * Unit tests for {@link ResourceCRLRevocationChecker} class. + * + * @author Marvin S. Addison + * @since 3.4.6 + * + */ +@RunWith(Parameterized.class) +public class ResourceCRLRevocationCheckerTests extends AbstractCRLRevocationCheckerTests { + /** Instance under test. */ + private final ResourceCRLRevocationChecker checker; + + /** + * Creates a new test instance with given parameters. + * + * @param checker Revocation checker instance. + * @param expiredCRLPolicy Policy instance for handling expired CRL data. + * @param certFiles File names of certificates to check. + * @param expected Expected result of check; null to indicate expected success. + */ + public ResourceCRLRevocationCheckerTests( + final ResourceCRLRevocationChecker checker, + final RevocationPolicy expiredCRLPolicy, + final String[] certFiles, + final GeneralSecurityException expected) { + + super(certFiles, expected); + + this.checker = checker; + this.checker.setExpiredCRLPolicy(expiredCRLPolicy); + try { + this.checker.afterPropertiesSet(); + } catch (final Exception e) { + throw new RuntimeException("ResourceCRLRevocationChecker initialization failed", e); + } + } + + /** + * Gets the unit test parameters. + * + * @return Test parameter data. + */ + @Parameters + public static Collection getTestParameters() { + final Collection params = new ArrayList<>(); + + final ThresholdExpiredCRLRevocationPolicy zeroThresholdPolicy = new ThresholdExpiredCRLRevocationPolicy(); + zeroThresholdPolicy.setThreshold(0); + + // Test case #1 + // Valid certificate on valid CRL data + params.add(new Object[] { + new ResourceCRLRevocationChecker(new ClassPathResource[] { + new ClassPathResource("userCA-valid.crl"), + }), + zeroThresholdPolicy, + new String[] {"user-valid.crt"}, + null, + }); + + // Test case #2 + // Revoked certificate on valid CRL data + params.add(new Object[] { + new ResourceCRLRevocationChecker(new ClassPathResource[] { + new ClassPathResource("userCA-valid.crl"), + new ClassPathResource("intermediateCA-valid.crl"), + new ClassPathResource("rootCA-valid.crl"), + }), + zeroThresholdPolicy, + new String[] {"user-revoked.crt", "userCA.crt", "intermediateCA.crt", "rootCA.crt" }, + new RevokedCertificateException(new Date(), new BigInteger("1")), + }); + + // Test case #3 + // Valid certificate on expired CRL data for head cert + params.add(new Object[] { + new ResourceCRLRevocationChecker(new ClassPathResource[] { + new ClassPathResource("userCA-expired.crl"), + new ClassPathResource("intermediateCA-valid.crl"), + new ClassPathResource("rootCA-valid.crl"), + }), + zeroThresholdPolicy, + new String[] {"user-valid.crt", "userCA.crt", "intermediateCA.crt", "rootCA.crt" }, + new ExpiredCRLException("test", new Date()), + }); + + // Test case #4 + // Valid certificate on expired CRL data for intermediate cert + params.add(new Object[] { + new ResourceCRLRevocationChecker(new ClassPathResource[] { + new ClassPathResource("userCA-valid.crl"), + new ClassPathResource("intermediateCA-expired.crl"), + new ClassPathResource("rootCA-valid.crl"), + }), + zeroThresholdPolicy, + new String[] {"user-valid.crt", "userCA.crt", "intermediateCA.crt", "rootCA.crt" }, + new ExpiredCRLException("test", new Date()), + }); + + // Test case #5 + // Valid certificate on expired CRL data with custom expiration + // policy to always allow expired CRL data + params.add(new Object[] { + new ResourceCRLRevocationChecker(new ClassPathResource[] { + new ClassPathResource("userCA-expired.crl"), + }), + new RevocationPolicy() { + @Override + public void apply(final X509CRL crl) {/* Do nothing to allow unconditionally */} + }, + new String[] {"user-valid.crt"}, + null, + }); + + return params; + } + + @Override + protected RevocationChecker getChecker() { + return this.checker; + } +} diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ThresholdExpiredCRLRevocationPolicyTests.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ThresholdExpiredCRLRevocationPolicyTests.java new file mode 100644 index 000000000000..65be5b65d17f --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ThresholdExpiredCRLRevocationPolicyTests.java @@ -0,0 +1,144 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import java.security.GeneralSecurityException; +import java.security.cert.X509CRL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +import javax.security.auth.x500.X500Principal; + +import org.jasig.cas.adaptors.x509.util.MockX509CRL; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + + +/** + * Unit test for {@link ThresholdExpiredCRLRevocationPolicy} class. + * + * @author Marvin S. Addison + * @since 3.4.7 + * + */ +@RunWith(Parameterized.class) +public class ThresholdExpiredCRLRevocationPolicyTests { + /** Policy instance under test. */ + private final ThresholdExpiredCRLRevocationPolicy policy; + + /** CRL to test. */ + private final X509CRL crl; + + /** Expected result of check; null for success */ + private final GeneralSecurityException expected; + + + /** + * Creates a new test instance with given parameters. + * + * @param policy Policy to test. + * @param crl CRL instance to apply policy to. + * @param expected Expected result of policy application; null to indicate expected success. + */ + public ThresholdExpiredCRLRevocationPolicyTests( + final ThresholdExpiredCRLRevocationPolicy policy, + final X509CRL crl, + final GeneralSecurityException expected) { + + this.policy = policy; + this.expected = expected; + this.crl = crl; + } + + /** + * Gets the unit test parameters. + * + * @return Test parameter data. + * @throws Exception if there is an exception getting the test parameters. + */ + @Parameters + public static Collection getTestParameters() throws Exception { + final Collection params = new ArrayList<>(); + + final Date now = new Date(); + final Date twoHoursAgo = new Date(now.getTime() - 7200000); + final Date oneHourAgo = new Date(now.getTime() - 3600000); + final Date halfHourAgo = new Date(now.getTime() - 1800000); + final X500Principal issuer = new X500Principal("CN=CAS"); + + // Test case #1 + // Expect expired for zero leniency on CRL expiring 1ms ago + final ThresholdExpiredCRLRevocationPolicy zeroThreshold = new ThresholdExpiredCRLRevocationPolicy(); + zeroThreshold.setThreshold(0); + params.add(new Object[] { + zeroThreshold, + new MockX509CRL(issuer, oneHourAgo, new Date(now.getTime() - 1)), + new ExpiredCRLException("CN=CAS", new Date()), + }); + + // Test case #2 + // Expect expired for 1h leniency on CRL expired 1 hour 1ms ago + final ThresholdExpiredCRLRevocationPolicy oneHourThreshold = new ThresholdExpiredCRLRevocationPolicy(); + oneHourThreshold.setThreshold(3600); + params.add(new Object[] { + oneHourThreshold, + new MockX509CRL(issuer, twoHoursAgo, new Date(oneHourAgo.getTime() - 1)), + new ExpiredCRLException("CN=CAS", new Date()), + }); + + // Test case #3 + // Expect valid for 1h leniency on CRL expired 30m ago + params.add(new Object[] { + oneHourThreshold, + new MockX509CRL(issuer, twoHoursAgo, halfHourAgo), + null, + }); + + return params; + } + + /** + * Test method for {@link ThresholdExpiredCRLRevocationPolicy#apply(java.security.cert.X509CRL)}. + */ + @Test + public void verifyApply() { + try { + this.policy.apply(this.crl); + if (this.expected != null) { + Assert.fail("Expected exception of type " + this.expected.getClass()); + } + } catch (final GeneralSecurityException e) { + if (this.expected == null) { + e.printStackTrace(); + Assert.fail("Revocation check failed unexpectedly with exception: " + e); + } else { + final Class expectedClass = this.expected.getClass(); + final Class actualClass = e.getClass(); + Assert.assertTrue( + String.format("Expected exception of type %s but got %s", expectedClass, actualClass), + expectedClass.isAssignableFrom(actualClass)); + } + } + } +} diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/WiringTests.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/WiringTests.java new file mode 100644 index 000000000000..aa95b04c2d13 --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/WiringTests.java @@ -0,0 +1,40 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + + +/** + * Verifies Spring IOC wiring for X.509 beans. + * + * @author Marvin S. Addison + * @since 3.4.6 + * + */ +public class WiringTests { + @Test + public void verifyWiring() { + try (final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("deployerConfigContext.xml")) { + Assert.assertTrue(context.getBeanDefinitionCount() > 0); + } + } +} diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/X509CredentialsAuthenticationHandlerTests.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/X509CredentialsAuthenticationHandlerTests.java new file mode 100644 index 000000000000..67029ce9f754 --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/X509CredentialsAuthenticationHandlerTests.java @@ -0,0 +1,288 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.handler.support; + +import edu.vt.middleware.crypt.util.CryptReader; +import org.jasig.cas.adaptors.x509.authentication.principal.X509CertificateCredential; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.DefaultHandlerResult; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.springframework.core.io.ClassPathResource; + +import javax.security.auth.login.FailedLoginException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; + +import static org.junit.Assert.*; + +/** + * Unit test for {@link X509CredentialsAuthenticationHandler} class. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.0.0.4 + * + */ +@RunWith(Parameterized.class) +public class X509CredentialsAuthenticationHandlerTests { + + /** Subject of test. */ + private final X509CredentialsAuthenticationHandler handler; + + /** Test authentication credential. */ + private final Credential credential; + + /** Expected result of supports test. */ + private final boolean expectedSupports; + + /** Expected authentication result. */ + private final Object expectedResult; + + + /** + * Creates a new test class instance with the given parameters. + * + * @param handler Test authentication handler. + * @param credential Test credential. + * @param supports Expected result of supports test. + * @param result Expected result of authentication test. + */ + public X509CredentialsAuthenticationHandlerTests( + final X509CredentialsAuthenticationHandler handler, + final Credential credential, + final boolean supports, + final Object result) { + + this.handler = handler; + this.credential = credential; + this.expectedSupports = supports; + this.expectedResult = result; + } + + /** + * Gets the unit test parameters. + * + * @return Test parameter data. + * + * @throws Exception On test data setup errors. + */ + @Parameters + public static Collection getTestParameters() throws Exception { + final Collection params = new ArrayList<>(); + + X509CredentialsAuthenticationHandler handler; + X509CertificateCredential credential; + + // Test case #1: Unsupported credential type + handler = new X509CredentialsAuthenticationHandler(); + handler.setTrustedIssuerDnPattern(".*"); + params.add(new Object[] {handler, new UsernamePasswordCredential(), false, null}); + + // Test case #2:Valid certificate + handler = new X509CredentialsAuthenticationHandler(); + handler.setTrustedIssuerDnPattern(".*"); + credential = new X509CertificateCredential(createCertificates("user-valid.crt")); + params.add(new Object[] {handler, credential, true, new DefaultHandlerResult(handler, credential, + new DefaultPrincipalFactory().createPrincipal(credential.getId())), + }); + + // Test case #3: Expired certificate + handler = new X509CredentialsAuthenticationHandler(); + handler.setTrustedIssuerDnPattern(".*"); + params.add(new Object[] { + handler, + new X509CertificateCredential(createCertificates("user-expired.crt")), + true, + new CertificateExpiredException(), + }); + + // Test case #4: Untrusted issuer + handler = new X509CredentialsAuthenticationHandler(); + handler.setTrustedIssuerDnPattern("CN=\\w+,OU=CAS,O=Jasig,L=Westminster,ST=Colorado,C=US"); + handler.setMaxPathLengthAllowUnspecified(true); + params.add(new Object[] {handler, new X509CertificateCredential(createCertificates("snake-oil.crt")), + true, new FailedLoginException(), + }); + + // Test case #5: Disallowed subject + handler = new X509CredentialsAuthenticationHandler(); + handler.setTrustedIssuerDnPattern(".*"); + handler.setSubjectDnPattern("CN=\\w+,OU=CAS,O=Jasig,L=Westminster,ST=Colorado,C=US"); + handler.setMaxPathLengthAllowUnspecified(true); + params.add(new Object[] { + handler, + new X509CertificateCredential(createCertificates("snake-oil.crt")), + true, + new FailedLoginException(), + }); + + // Test case #6: Check key usage on a cert without keyUsage extension + handler = new X509CredentialsAuthenticationHandler(); + handler.setTrustedIssuerDnPattern(".*"); + handler.setCheckKeyUsage(true); + credential = new X509CertificateCredential(createCertificates("user-valid.crt")); + params.add(new Object[] { + handler, + credential, + true, + new DefaultHandlerResult(handler, credential, new DefaultPrincipalFactory().createPrincipal(credential.getId())), + }); + + // Test case #7: Require key usage on a cert without keyUsage extension + handler = new X509CredentialsAuthenticationHandler(); + handler.setTrustedIssuerDnPattern(".*"); + handler.setCheckKeyUsage(true); + handler.setRequireKeyUsage(true); + params.add(new Object[] { + handler, + new X509CertificateCredential(createCertificates("user-valid.crt")), + true, new FailedLoginException(), + }); + + // Test case #8: Require key usage on a cert with acceptable keyUsage extension values + handler = new X509CredentialsAuthenticationHandler(); + handler.setTrustedIssuerDnPattern(".*"); + handler.setCheckKeyUsage(true); + handler.setRequireKeyUsage(true); + credential = new X509CertificateCredential(createCertificates("user-valid-keyUsage.crt")); + params.add(new Object[] { + handler, + credential, + true, + new DefaultHandlerResult(handler, credential, new DefaultPrincipalFactory().createPrincipal(credential.getId())), + }); + + // Test case #9: Require key usage on a cert with unacceptable keyUsage extension values + handler = new X509CredentialsAuthenticationHandler(); + handler.setTrustedIssuerDnPattern(".*"); + handler.setCheckKeyUsage(true); + handler.setRequireKeyUsage(true); + params.add(new Object[] { + handler, + new X509CertificateCredential(createCertificates("user-invalid-keyUsage.crt")), + true, + new FailedLoginException(), + }); + + //=================================== + // Revocation tests + //=================================== + ResourceCRLRevocationChecker checker; + + // Test case #10: Valid certificate with CRL checking + handler = new X509CredentialsAuthenticationHandler(); + checker = new ResourceCRLRevocationChecker(new ClassPathResource("userCA-valid.crl")); + checker.afterPropertiesSet(); + handler.setRevocationChecker(checker); + handler.setTrustedIssuerDnPattern(".*"); + credential = new X509CertificateCredential(createCertificates("user-valid.crt")); + params.add(new Object[] { + handler, + new X509CertificateCredential(createCertificates("user-valid.crt")), + true, + new DefaultHandlerResult(handler, credential, new DefaultPrincipalFactory().createPrincipal(credential.getId())), + }); + + // Test case #11: Revoked end user certificate + handler = new X509CredentialsAuthenticationHandler(); + checker = new ResourceCRLRevocationChecker(new ClassPathResource("userCA-valid.crl")); + checker.afterPropertiesSet(); + handler.setRevocationChecker(checker); + handler.setTrustedIssuerDnPattern(".*"); + params.add(new Object[] { + handler, + new X509CertificateCredential(createCertificates("user-revoked.crt")), + true, + new RevokedCertificateException(null, null), + }); + + // Test case #12: Valid certificate on expired CRL data + final ThresholdExpiredCRLRevocationPolicy zeroThresholdPolicy = new ThresholdExpiredCRLRevocationPolicy(); + zeroThresholdPolicy.setThreshold(0); + handler = new X509CredentialsAuthenticationHandler(); + handler.setTrustedIssuerDnPattern(".*"); + checker = new ResourceCRLRevocationChecker(new ClassPathResource("userCA-expired.crl")); + checker.setExpiredCRLPolicy(zeroThresholdPolicy); + checker.afterPropertiesSet(); + handler.setRevocationChecker(checker); + params.add(new Object[] { + handler, + new X509CertificateCredential(createCertificates("user-valid.crt")), + true, + new ExpiredCRLException(null, null), + }); + + return params; + } + + /** + * Tests the {@link X509CredentialsAuthenticationHandler#authenticate(org.jasig.cas.authentication.Credential)} method. + */ + @Test + public void verifyAuthenticate() { + try { + if (this.handler.supports(this.credential)) { + final HandlerResult result = this.handler.authenticate(this.credential); + if (this.expectedResult instanceof DefaultHandlerResult) { + assertEquals(this.expectedResult, result); + } else { + fail("Authentication succeeded when it should have failed with " + this.expectedResult); + } + } + } catch (final Exception e) { + if (this.expectedResult instanceof Exception) { + assertEquals(this.expectedResult.getClass(), e.getClass()); + } else { + fail("Authentication failed when it should have succeeded."); + } + } + } + + /** + * Tests the {@link X509CredentialsAuthenticationHandler#supports(org.jasig.cas.authentication.Credential)} method. + */ + @Test + public void verifySupports() { + assertEquals(this.expectedSupports, this.handler.supports(this.credential)); + } + + protected static X509Certificate[] createCertificates(final String ... files) { + final X509Certificate[] certs = new X509Certificate[files.length]; + + int i = 0; + for (final String file : files) { + try { + certs[i++] = (X509Certificate) CryptReader.readCertificate( + new ClassPathResource(file).getInputStream()); + } catch (final Exception e) { + throw new RuntimeException("Error creating certificate at " + file, e); + } + } + return certs; + } +} + diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ldap/LdaptiveResourceCRLFetcherTests.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ldap/LdaptiveResourceCRLFetcherTests.java new file mode 100644 index 000000000000..258915525a00 --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ldap/LdaptiveResourceCRLFetcherTests.java @@ -0,0 +1,87 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.adaptors.x509.authentication.handler.support.ldap; + +import java.security.cert.X509Certificate; +import net.sf.ehcache.Cache; +import net.sf.ehcache.CacheManager; +import org.jasig.cas.adaptors.x509.authentication.handler.support.AbstractX509LdapTests; +import org.jasig.cas.adaptors.x509.authentication.handler.support.AllowRevocationPolicy; +import org.jasig.cas.adaptors.x509.authentication.handler.support.CRLDistributionPointRevocationChecker; +import org.jasig.cas.adaptors.x509.util.CertUtils; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.io.ClassPathResource; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + + +/** + * Test cases for {@link LdaptiveResourceCRLFetcher} + * @author Misagh Moayyed + * @since 4.1 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration({"/x509-ldap-context.xml"}) +public class LdaptiveResourceCRLFetcherTests extends AbstractX509LdapTests { + + + @Autowired + @Qualifier("ldapCertFetcher") + private LdaptiveResourceCRLFetcher fetcher; + + @BeforeClass + public static void bootstrap() throws Exception { + AbstractX509LdapTests.bootstrap(); + } + + @Test + public void getCrlFromLdap() throws Exception { + CacheManager.getInstance().removeAllCaches(); + final Cache cache = new Cache("crlCache-1", 100, false, false, 20, 10); + CacheManager.getInstance().addCache(cache); + + for (int i = 0; i < 10; i++) { + final CRLDistributionPointRevocationChecker checker = new CRLDistributionPointRevocationChecker(cache, fetcher); + checker.setThrowOnFetchFailure(true); + checker.setUnavailableCRLPolicy(new AllowRevocationPolicy()); + final X509Certificate cert = CertUtils.readCertificate(new ClassPathResource("ldap-crl.crt")); + checker.check(cert); + } + } + + @Test + public void getCrlFromLdapWithNoCaching() throws Exception { + for (int i = 0; i < 10; i++) { + CacheManager.getInstance().removeAllCaches(); + final Cache cache = new Cache("crlCache-1", 100, false, false, 20, 10); + CacheManager.getInstance().addCache(cache); + final CRLDistributionPointRevocationChecker checker = new CRLDistributionPointRevocationChecker(cache, fetcher); + checker.setThrowOnFetchFailure(true); + checker.setUnavailableCRLPolicy(new AllowRevocationPolicy()); + final X509Certificate cert = CertUtils.readCertificate(new ClassPathResource("ldap-crl.crt")); + checker.check(cert); + } + } + +} diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ldap/PoolingLdaptiveResourceCRLFetcherTests.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ldap/PoolingLdaptiveResourceCRLFetcherTests.java new file mode 100644 index 000000000000..879755dad239 --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/handler/support/ldap/PoolingLdaptiveResourceCRLFetcherTests.java @@ -0,0 +1,87 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.adaptors.x509.authentication.handler.support.ldap; + +import java.security.cert.X509Certificate; +import net.sf.ehcache.Cache; +import net.sf.ehcache.CacheManager; +import org.jasig.cas.adaptors.x509.authentication.handler.support.AbstractX509LdapTests; +import org.jasig.cas.adaptors.x509.authentication.handler.support.AllowRevocationPolicy; +import org.jasig.cas.adaptors.x509.authentication.handler.support.CRLDistributionPointRevocationChecker; +import org.jasig.cas.adaptors.x509.util.CertUtils; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.io.ClassPathResource; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + + +/** + * Test cases for {@link PoolingLdaptiveResourceCRLFetcher} + * @author Misagh Moayyed + * @since 4.1 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration({"/x509-ldap-context.xml"}) +public class PoolingLdaptiveResourceCRLFetcherTests extends AbstractX509LdapTests { + + + @Autowired + @Qualifier("poolingLdapCertFetcher") + private PoolingLdaptiveResourceCRLFetcher fetcher; + + @BeforeClass + public static void bootstrap() throws Exception { + AbstractX509LdapTests.bootstrap(); + } + + @Test + public void getCrlFromLdap() throws Exception { + CacheManager.getInstance().removeAllCaches(); + final Cache cache = new Cache("crlCache-1", 100, false, false, 20, 10); + CacheManager.getInstance().addCache(cache); + + for (int i = 0; i < 10; i++) { + final CRLDistributionPointRevocationChecker checker = new CRLDistributionPointRevocationChecker(cache, fetcher); + checker.setThrowOnFetchFailure(true); + checker.setUnavailableCRLPolicy(new AllowRevocationPolicy()); + final X509Certificate cert = CertUtils.readCertificate(new ClassPathResource("ldap-crl.crt")); + checker.check(cert); + } + } + + @Test + public void getCrlFromLdapWithNoCaching() throws Exception { + for (int i = 0; i < 10; i++) { + CacheManager.getInstance().removeAllCaches(); + final Cache cache = new Cache("crlCache-1", 100, false, false, 20, 10); + CacheManager.getInstance().addCache(cache); + final CRLDistributionPointRevocationChecker checker = new CRLDistributionPointRevocationChecker(cache, fetcher); + checker.setThrowOnFetchFailure(true); + checker.setUnavailableCRLPolicy(new AllowRevocationPolicy()); + final X509Certificate cert = CertUtils.readCertificate(new ClassPathResource("ldap-crl.crt")); + checker.check(cert); + } + } + +} diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/AbstractX509CertificateTests.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/AbstractX509CertificateTests.java new file mode 100644 index 000000000000..98377dfae095 --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/AbstractX509CertificateTests.java @@ -0,0 +1,214 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.principal; + +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Principal; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.Set; + +/** + * @author Marvin S. Addison + * @since 3.0.0 + */ +public abstract class AbstractX509CertificateTests { + + public static final X509Certificate VALID_CERTIFICATE = new CasX509Certificate( + true); + + public static final X509Certificate INVALID_CERTIFICATE = new CasX509Certificate( + false); + + protected static class CasX509Certificate extends X509Certificate { + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = -4449243195531417769L; + + private final boolean valid; + + protected CasX509Certificate(final boolean valid) { + this.valid = valid; + } + + @Override + public void checkValidity() throws CertificateExpiredException, + CertificateNotYetValidException { + if (!this.valid) { + throw new CertificateExpiredException(); + } + } + + @Override + public void checkValidity(final Date arg0) + throws CertificateExpiredException, CertificateNotYetValidException { + if (!this.valid) { + throw new CertificateExpiredException(); + } + } + + @Override + public int getBasicConstraints() { + return -1; + } + + @Override + public Principal getIssuerDN() { + return new Principal(){ + + @Override + public String getName() { + return "CN=Jasig,DC=jasig,DC=org"; + } + }; + } + + @Override + public boolean[] getIssuerUniqueID() { + return null; + } + + @Override + public boolean[] getKeyUsage() { + return null; + } + + @Override + public Date getNotAfter() { + return null; + } + + @Override + public Date getNotBefore() { + return null; + } + + @Override + public BigInteger getSerialNumber() { + return new BigInteger("500000"); + } + + @Override + public String getSigAlgName() { + return null; + } + + @Override + public String getSigAlgOID() { + return null; + } + + @Override + public byte[] getSigAlgParams() { + return null; + } + + @Override + public byte[] getSignature() { + return null; + } + + @Override + public Principal getSubjectDN() { + return new Principal(){ + + @Override + public String getName() { + return "CN=CAS,DC=jasig,DC=org"; + } + + }; + } + + @Override + public boolean[] getSubjectUniqueID() { + return null; + } + + @Override + public byte[] getTBSCertificate() throws CertificateEncodingException { + return null; + } + + @Override + public int getVersion() { + return 0; + } + + @Override + public Set getCriticalExtensionOIDs() { + return null; + } + + @Override + public byte[] getExtensionValue(final String arg0) { + return null; + } + + @Override + public Set getNonCriticalExtensionOIDs() { + return null; + } + + @Override + public boolean hasUnsupportedCriticalExtension() { + return false; + } + + @Override + public byte[] getEncoded() throws CertificateEncodingException { + return null; + } + + @Override + public PublicKey getPublicKey() { + return null; + } + + @Override + public String toString() { + return "CasX509Certficate"; + } + + @Override + public void verify(final PublicKey arg0, final String arg1) + throws CertificateException, NoSuchAlgorithmException, + InvalidKeyException, NoSuchProviderException, SignatureException { + // nothing to do right now + } + + @Override + public void verify(final PublicKey arg0) throws CertificateException, + NoSuchAlgorithmException, InvalidKeyException, + NoSuchProviderException, SignatureException { + // nothing to do right now + } + } +} diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SerialNumberAndIssuerDNPrincipalResolverTests.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SerialNumberAndIssuerDNPrincipalResolverTests.java new file mode 100644 index 000000000000..d297dd151375 --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SerialNumberAndIssuerDNPrincipalResolverTests.java @@ -0,0 +1,63 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.principal; + +import static org.junit.Assert.*; + +import java.security.cert.X509Certificate; + +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.junit.Test; + +/** + * @author Scott Battaglia + * @author Jan Van der Velpen + * @since 3.0.0.6 + * + */ +public class X509SerialNumberAndIssuerDNPrincipalResolverTests extends AbstractX509CertificateTests { + + private final X509SerialNumberAndIssuerDNPrincipalResolver + resolver = new X509SerialNumberAndIssuerDNPrincipalResolver(); + + @Test + public void verifyResolvePrincipalInternal() { + final X509CertificateCredential c = new X509CertificateCredential(new X509Certificate[] {VALID_CERTIFICATE}); + c.setCertificate(VALID_CERTIFICATE); + + + final String value = "SERIALNUMBER=" + + VALID_CERTIFICATE.getSerialNumber().toString() + + ", " + VALID_CERTIFICATE.getIssuerDN().getName(); + + assertEquals(value, this.resolver.resolve(c).getId()); + } + + @Test + public void verifySupport() { + final X509CertificateCredential c = new X509CertificateCredential(new X509Certificate[] {VALID_CERTIFICATE}); + assertTrue(this.resolver.supports(c)); + } + + @Test + public void verifySupportFalse() { + assertFalse(this.resolver.supports(new UsernamePasswordCredential())); + } + +} diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SerialNumberPrincipalResolverTests.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SerialNumberPrincipalResolverTests.java new file mode 100644 index 000000000000..1bb0d0d53b88 --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SerialNumberPrincipalResolverTests.java @@ -0,0 +1,58 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.principal; + +import static org.junit.Assert.*; + +import java.security.cert.X509Certificate; + +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.junit.Test; + +/** + * @author Scott Battaglia + * @author Jan Van der Velpen + * @since 3.0.0.6 + * + */ +public class X509SerialNumberPrincipalResolverTests +extends AbstractX509CertificateTests { + + private final X509SerialNumberPrincipalResolver resolver = new X509SerialNumberPrincipalResolver(); + + @Test + public void verifyResolvePrincipalInternal() { + final X509CertificateCredential c = new X509CertificateCredential(new X509Certificate[] {VALID_CERTIFICATE}); + c.setCertificate(VALID_CERTIFICATE); + + assertEquals(VALID_CERTIFICATE.getSerialNumber().toString(), this.resolver.resolve(c).getId()); + } + + @Test + public void verifySupport() { + final X509CertificateCredential c = new X509CertificateCredential(new X509Certificate[] {VALID_CERTIFICATE}); + assertTrue(this.resolver.supports(c)); + } + + @Test + public void verifySupportFalse() { + assertFalse(this.resolver.supports(new UsernamePasswordCredential())); + } + +} diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectAlternativeNameUPNPrincipalResolverTests.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectAlternativeNameUPNPrincipalResolverTests.java new file mode 100644 index 000000000000..64f367c9acfc --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectAlternativeNameUPNPrincipalResolverTests.java @@ -0,0 +1,87 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.principal; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.io.FileInputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; + +/** + * Unit test for {@link org.jasig.cas.adaptors.x509.authentication.principal.X509SubjectAlternativeNameUPNPrincipalResolver}. + * + * @author Dmitriy Kopylenko + * @since 3.0.0 + */ +@RunWith(Parameterized.class) +public class X509SubjectAlternativeNameUPNPrincipalResolverTests { + + private X509Certificate certificate; + private final X509SubjectAlternativeNameUPNPrincipalResolver resolver; + private final String expected; + + /** + * Creates a new test instance with the given parameters. + * + * @param certPath path to the cert + * @param expectedResult the result expected from the test + */ + public X509SubjectAlternativeNameUPNPrincipalResolverTests( + final String certPath, + final String expectedResult) { + + this.resolver = new X509SubjectAlternativeNameUPNPrincipalResolver(); + try { + this.certificate = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate( + new FileInputStream(getClass().getResource(certPath).getPath())); + } catch (final Exception e) { + Assert.fail(String.format("Error parsing certificate %s: %s", certPath, e.getMessage())); + } + this.expected = expectedResult; + } + + /** + * Gets the unit test parameters. + * + * @return Test parameter data. + */ + @Parameters + public static Collection getTestParameters() { + final Collection params = new ArrayList<>(); + + params.add(new Object[] { + "/x509-san-upn-resolver.crt", + "test-user@some-company-domain" + }); + return params; + } + + @Test + public void verifyResolvePrincipalInternal() { + Assert.assertEquals(this.expected, this.resolver.resolvePrincipalInternal(this.certificate)); + } + +} diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectDNPrincipalResolverTests.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectDNPrincipalResolverTests.java new file mode 100644 index 000000000000..e564eafbfe33 --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectDNPrincipalResolverTests.java @@ -0,0 +1,57 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.principal; + +import static org.junit.Assert.*; + +import java.security.cert.X509Certificate; + +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.junit.Test; + +/** + * @author Scott Battaglia + * @author Jan Van der Velpen + * @since 3.0.0.6 + * + */ +public class X509SubjectDNPrincipalResolverTests extends AbstractX509CertificateTests { + + private final X509SubjectDNPrincipalResolver + resolver = new X509SubjectDNPrincipalResolver(); + + @Test + public void verifyResolvePrincipalInternal() { + final X509CertificateCredential c = new X509CertificateCredential(new X509Certificate[] {VALID_CERTIFICATE}); + c.setCertificate(VALID_CERTIFICATE); + assertEquals(VALID_CERTIFICATE.getSubjectDN().getName(), this.resolver.resolve(c).getId()); + } + + @Test + public void verifySupport() { + final X509CertificateCredential c = new X509CertificateCredential(new X509Certificate[] {VALID_CERTIFICATE}); + assertTrue(this.resolver.supports(c)); + } + + @Test + public void verifySupportFalse() { + assertFalse(this.resolver.supports(new UsernamePasswordCredential())); + } + +} diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectPrincipalResolverTests.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectPrincipalResolverTests.java new file mode 100644 index 000000000000..79f088147c3e --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/authentication/principal/X509SubjectPrincipalResolverTests.java @@ -0,0 +1,126 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.authentication.principal; + +import java.io.FileInputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + * Unit test for {@link X509SubjectPrincipalResolver}. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +@RunWith(Parameterized.class) +public class X509SubjectPrincipalResolverTests { + + private X509Certificate certificate; + private final X509SubjectPrincipalResolver resolver; + private final String expected; + + /** + * Creates a new test instance with the given parameters. + * + * @param certPath path to the cert + * @param descriptor the descriptor + * @param expectedResult the expected result + */ + public X509SubjectPrincipalResolverTests( + final String certPath, + final String descriptor, + final String expectedResult) { + + this.resolver = new X509SubjectPrincipalResolver(); + this.resolver.setDescriptor(descriptor); + try { + this.certificate = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate( + new FileInputStream(certPath)); + } catch (final Exception e) { + Assert.fail(String.format("Error parsing certificate %s: %s", certPath, e.getMessage())); + } + this.expected = expectedResult; + } + + /** + * Gets the unit test parameters. + * + * @return Test parameter data. + */ + @Parameters + public static Collection getTestParameters() { + final Collection params = new ArrayList<>(); + + // Test case #1 + // Use CN for principal ID + params.add(new Object[] { + "target/test-classes/x509-ctop-resolver-hizzy.crt", + "$CN", + "Hizzogarthington I.S. Pleakinsense" + }); + + // Test case #2 + // Use email address for principal ID + params.add(new Object[] { + "target/test-classes/x509-ctop-resolver-hizzy.crt", + "$EMAILADDRESS", + "hizzy@vt.edu" + }); + + // Test case #2 + // Use combination of ou and cn for principal ID + params.add(new Object[] { + "target/test-classes/x509-ctop-resolver-hizzy.crt", + "$OU $CN", + "Middleware Hizzogarthington I.S. Pleakinsense" + }); + + // Test case #3 + // Use combination of serial number and cn for principal ID + params.add(new Object[] { + "target/test-classes/x509-ctop-resolver-gazzo.crt", + "$CN:$SERIALNUMBER", + "Gazzaloddi P. Wishwashington:271828183" + }); + + // Test case #4 + // Build principal ID from multivalued attributes + params.add(new Object[] { + "target/test-classes/x509-ctop-resolver-jacky.crt", + "$UID@$DC.$DC", + "jacky@vt.edu" + }); + + return params; + } + + @Test + public void verifyResolvePrincipalInternal() { + Assert.assertEquals(this.expected, this.resolver.resolvePrincipalInternal(this.certificate)); + } + +} diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/util/MockWebServer.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/util/MockWebServer.java new file mode 100644 index 000000000000..629a3026bcb5 --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/util/MockWebServer.java @@ -0,0 +1,186 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.Resource; + + +/** + * Provides a simple HTTP Web server that can serve out a single resource for + * all requests. SSL/TLS is not supported. + * + * @author Marvin S. Addison + * @since 3.4.6 + * + */ +public class MockWebServer { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + /** Request handler. */ + private Worker worker; + + /** Controls the worker thread. */ + private Thread workerThread; + + + /** + * Creates a new server that listens for requests on the given port and + * serves the given resource for all requests. + * + * @param port Server listening port. + * @param resource Resource to serve. + * @param contentType MIME content type of resource to serve. + */ + public MockWebServer(final int port, final Resource resource, final String contentType) { + try { + this.worker = new Worker(new ServerSocket(port), resource, contentType); + } catch (final IOException e) { + throw new RuntimeException("Cannot create Web server", e); + } + } + + /** Starts the Web server so it can accept requests on the listening port. */ + public void start() { + this.workerThread = new Thread(this.worker, "MockWebServer.Worker"); + this.workerThread.start(); + } + + /** Stops the Web server after processing any pending requests. */ + public void stop() { + if (!isRunning()) { + return; + } + this.worker.stop(); + try { + this.workerThread.join(); + } catch (final InterruptedException e) { + logger.error(e.getMessage(), e); + } + } + + /** + * Determines whether the server is running or not. + * + * @return True if server is running, false otherwise. + */ + public boolean isRunning() { + return this.workerThread.isAlive(); + } + + /** + * Worker class handles request processing. + */ + final class Worker implements Runnable { + /** Server always returns HTTP 200 response. */ + private static final String STATUS_LINE = "HTTP/1.1 200 Success\r\n"; + + /** Separates HTTP header from body. */ + private static final String SEPARATOR = "\r\n"; + + /** Response buffer size. */ + private static final int BUFFER_SIZE = 2048; + + /** Run flag. */ + private boolean running; + + /** Server socket. */ + private final ServerSocket serverSocket; + + /** Resource to serve. */ + private final Resource resource; + + /** MIME content type of resource to serve. */ + private final String contentType; + + + /** + * Creates a request-handling worker that listens for requests on the + * given socket and serves the given resource for all requests. + * + * @param sock Server socket. + * @param resource Single resource to serve. + * @param contentType MIME content type of resource to serve. + */ + public Worker(final ServerSocket sock, final Resource resource, final String contentType) { + this.serverSocket = sock; + this.resource = resource; + this.contentType = contentType; + this.running = true; + } + + @Override + public void run() { + while (this.running) { + try { + writeResponse(this.serverSocket.accept()); + Thread.sleep(500); + } catch (final SocketException se) { + logger.debug("Stopping on socket close."); + this.running = false; + } catch (final Exception e) { + logger.error(e.getMessage(), e); + } + } + } + + public void stop() { + try { + this.serverSocket.close(); + } catch (final IOException e) { + logger.trace("Exception when closing the server socket: {}", e.getMessage()); + } + } + + private void writeResponse(final Socket socket) throws IOException { + logger.debug("Socket response for resource {}", resource.getFilename()); + final OutputStream out = socket.getOutputStream(); + out.write(STATUS_LINE.getBytes()); + out.write(header("Content-Length", this.resource.contentLength())); + out.write(header("Content-Type", this.contentType)); + out.write(SEPARATOR.getBytes()); + + final byte[] buffer = new byte[BUFFER_SIZE]; + try (final InputStream in = this.resource.getInputStream()) { + int count = 0; + while ((count = in.read(buffer)) > -1) { + out.write(buffer, 0, count); + } + } + logger.debug("Wrote response for resource {} for {}", + resource.getFilename(), + resource.contentLength()); + + socket.shutdownOutput(); + } + + private byte[] header(final String name, final Object value) { + return String.format("%s: %s\r\n", name, value).getBytes(); + } + } +} diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/util/MockX509CRL.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/util/MockX509CRL.java new file mode 100644 index 000000000000..ba6ff6e64cec --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/util/MockX509CRL.java @@ -0,0 +1,241 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.util; + +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Principal; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.cert.CRLException; +import java.security.cert.Certificate; +import java.security.cert.X509CRL; +import java.security.cert.X509CRLEntry; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.Set; + +import javax.security.auth.x500.X500Principal; + + +/** + * Mock implementation of X.509 CRL. + * + * @author Marvin S. Addison + * @since 3.4.6 + * + */ +public class MockX509CRL extends X509CRL { + /** Issuer name */ + private final X500Principal issuer; + + /** Instant CRL was issued. */ + private final Date thisUpdate; + + /** Instant on which next CRL update expected. */ + private final Date nextUpdate; + + /** + * Creates a new instance with given parameters. + * + * @param issuer CRL issuer. + * @param thisUpdate Instant CRL was issued. + * @param nextUpdate Instant where next CRL update is expected. + */ + public MockX509CRL(final X500Principal issuer, final Date thisUpdate, final Date nextUpdate) { + this.issuer = issuer; + this.thisUpdate = thisUpdate; + this.nextUpdate = nextUpdate; + } + + /** + * @see java.security.cert.X509Extension#getCriticalExtensionOIDs() + */ + @Override + public Set getCriticalExtensionOIDs() { + return null; + } + + /** + * @see java.security.cert.X509Extension#getExtensionValue(java.lang.String) + */ + @Override + public byte[] getExtensionValue(final String oid) { + return null; + } + + /** + * @see java.security.cert.X509Extension#getNonCriticalExtensionOIDs() + */ + @Override + public Set getNonCriticalExtensionOIDs() { + return null; + } + + /** + * @see java.security.cert.X509Extension#hasUnsupportedCriticalExtension() + */ + @Override + public boolean hasUnsupportedCriticalExtension() { + return false; + } + + /** + * @see java.security.cert.X509CRL#getEncoded() + */ + @Override + public byte[] getEncoded() throws CRLException { + return null; + } + + /** + * @see java.security.cert.X509CRL#getIssuerDN() + */ + @Override + public Principal getIssuerDN() { + return this.issuer; + } + + /** + * @see java.security.cert.X509CRL#getNextUpdate() + */ + @Override + public Date getNextUpdate() { + return this.nextUpdate; + } + + /** + * @see java.security.cert.X509CRL#getRevokedCertificate(java.math.BigInteger) + */ + @Override + public X509CRLEntry getRevokedCertificate(final BigInteger serialNumber) { + return null; + } + + /** + * @see java.security.cert.X509CRL#getRevokedCertificates() + */ + @Override + public Set< ? extends X509CRLEntry> getRevokedCertificates() { + return null; + } + + /** + * @see java.security.cert.X509CRL#getSigAlgName() + */ + @Override + public String getSigAlgName() { + return "SHA1"; + } + + /** + * @see java.security.cert.X509CRL#getSigAlgOID() + */ + @Override + public String getSigAlgOID() { + return "1.3.14.3.2.26"; + } + + /** + * @see java.security.cert.X509CRL#getSigAlgParams() + */ + @Override + public byte[] getSigAlgParams() { + return null; + } + + /** + * @see java.security.cert.X509CRL#getSignature() + */ + @Override + public byte[] getSignature() { + return null; + } + + /** + * @see java.security.cert.X509CRL#getTBSCertList() + */ + @Override + public byte[] getTBSCertList() throws CRLException { + return null; + } + + /** + * @see java.security.cert.X509CRL#getThisUpdate() + */ + @Override + public Date getThisUpdate() { + return this.thisUpdate; + } + + /** + * @see java.security.cert.X509CRL#getVersion() + */ + @Override + public int getVersion() { + return 0; + } + + /** + * @see java.security.cert.X509CRL#verify(java.security.PublicKey) + */ + @Override + public void verify(final PublicKey key) throws CRLException, + NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, + SignatureException { + // Do nothing to indicate valid signature + } + + /** + * @see java.security.cert.X509CRL#verify(java.security.PublicKey, java.lang.String) + */ + @Override + public void verify(final PublicKey key, final String sigProvider) throws CRLException, + NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, + SignatureException { + // Do nothing to indicate valid signature + } + + /** + * @see java.security.cert.CRL#isRevoked(java.security.cert.Certificate) + */ + @Override + public boolean isRevoked(final Certificate cert) { + if (cert instanceof X509Certificate) { + final X509Certificate xcert = (X509Certificate) cert; + for (final X509CRLEntry entry : getRevokedCertificates()) { + if (entry.getSerialNumber().equals(xcert.getSerialNumber())) { + return true; + } + } + } + return false; + } + + /** + * @see java.security.cert.CRL#toString() + */ + @Override + public String toString() { + return "MockX509CRL for " + this.issuer; + } + +} diff --git a/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/web/flow/X509CertificateCredentialsNonInteractiveActionTests.java b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/web/flow/X509CertificateCredentialsNonInteractiveActionTests.java new file mode 100644 index 000000000000..ea3e7b3f09be --- /dev/null +++ b/cas-server-support-x509/src/test/java/org/jasig/cas/adaptors/x509/web/flow/X509CertificateCredentialsNonInteractiveActionTests.java @@ -0,0 +1,100 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.adaptors.x509.web.flow; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; + +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.jasig.cas.CentralAuthenticationServiceImpl; +import org.jasig.cas.adaptors.x509.authentication.handler.support.X509CredentialsAuthenticationHandler; +import org.jasig.cas.adaptors.x509.authentication.principal.AbstractX509CertificateTests; +import org.jasig.cas.adaptors.x509.authentication.principal.X509SerialNumberPrincipalResolver; +import org.jasig.cas.authentication.AuthenticationHandler; +import org.jasig.cas.authentication.AuthenticationManager; +import org.jasig.cas.authentication.PolicyBasedAuthenticationManager; +import org.jasig.cas.authentication.principal.PrincipalResolver; +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.logout.LogoutManager; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.ticket.registry.DefaultTicketRegistry; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.test.MockRequestContext; + +/** + * @author Marvin S. Addison + * @since 3.0.0 + */ +public class X509CertificateCredentialsNonInteractiveActionTests extends AbstractX509CertificateTests { + + private X509CertificateCredentialsNonInteractiveAction action; + + @Before + public void setUp() throws Exception { + this.action = new X509CertificateCredentialsNonInteractiveAction(); + final Map idGenerators = new HashMap<>(); + idGenerators.put(SimpleWebApplicationServiceImpl.class.getName(), new DefaultUniqueTicketIdGenerator()); + + + final X509CredentialsAuthenticationHandler handler = new X509CredentialsAuthenticationHandler(); + handler.setTrustedIssuerDnPattern("CN=\\w+,DC=jasig,DC=org"); + + final AuthenticationManager authenticationManager = new PolicyBasedAuthenticationManager( + Collections.singletonMap( + handler, new X509SerialNumberPrincipalResolver())); + + final CentralAuthenticationServiceImpl centralAuthenticationService = new CentralAuthenticationServiceImpl( + new DefaultTicketRegistry(), null, authenticationManager, new DefaultUniqueTicketIdGenerator(), + idGenerators, new NeverExpiresExpirationPolicy(), new NeverExpiresExpirationPolicy(), + mock(ServicesManager.class), mock(LogoutManager.class)); + + this.action.setCentralAuthenticationService(centralAuthenticationService); + this.action.afterPropertiesSet(); + } + + @Test + public void verifyNoCredentialsResultsInError() throws Exception { + final MockRequestContext context = new MockRequestContext(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), new MockHttpServletRequest(), new MockHttpServletResponse())); + assertEquals("error", this.action.execute(context).getId()); + } + + @Test + public void verifyCredentialsResultsInSuccess() throws Exception { + final MockRequestContext context = new MockRequestContext(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setAttribute("javax.servlet.request.X509Certificate", new X509Certificate[] {VALID_CERTIFICATE}); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + assertEquals("success", this.action.execute(context).getId()); + } +} diff --git a/cas-server-support-x509/src/test/resources/deployerConfigContext.xml b/cas-server-support-x509/src/test/resources/deployerConfigContext.xml new file mode 100644 index 000000000000..7b98fd348e93 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/deployerConfigContext.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-support-x509/src/test/resources/intermediateCA-expired.crl b/cas-server-support-x509/src/test/resources/intermediateCA-expired.crl new file mode 100644 index 000000000000..256d54a23fa9 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/intermediateCA-expired.crl @@ -0,0 +1,12 @@ +-----BEGIN X509 CRL----- +MIIB0DCBuQIBATANBgkqhkiG9w0BAQUFADB3MQswCQYDVQQGEwJVUzERMA8GA1UE +CBMIQ29sb3JhZG8xFDASBgNVBAcTC1dlc3RtaW5zdGVyMQ4wDAYDVQQKEwVKYXNp +ZzEMMAoGA1UECxMDQ0FTMSEwHwYDVQQDExhDQVMgVGVzdCBJbnRlcm1lZGlhdGUg +Q0EXDTExMDEyMTE2MjI1OFoXDTExMDEyMTE3MjI1OFqgDjAMMAoGA1UdFAQDAgEG +MA0GCSqGSIb3DQEBBQUAA4IBAQBUKeRdbeRr6ynCB9Ddwrfwyye3i+osWBei3+kp +iMC0viGjbbWEt0Z/cFFqMW0HetayjYdTKIzxL1wU6TINf/cZpBWjyo+8boAhDO8K +wftpRCbxD2vbiafpQ7i1nhQqRNjOCrvpod90gdOSSOIAYOW8l5OTDbqDtR1IIhQu +/TMdMPQ8HAnekTLNcxl2jQ93oZklKEq0O3jUVw64NEOzQ8hXUhmI3yvvtaqMSCpz +KfF2ab5Wgdlfw1HmQ7sn0o/k0WTq+XWikLdW6Q9JZk4jSbd+MpxgnjPZO1n8M9Cn +kHgLfrfOR8JlNJP3NLqz9mzrNeJ9wxch8d7RauIPAAQTUDB0 +-----END X509 CRL----- diff --git a/cas-server-support-x509/src/test/resources/intermediateCA-valid.crl b/cas-server-support-x509/src/test/resources/intermediateCA-valid.crl new file mode 100644 index 000000000000..0ad05e48cc10 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/intermediateCA-valid.crl @@ -0,0 +1,12 @@ +-----BEGIN X509 CRL----- +MIIB0DCBuQIBATANBgkqhkiG9w0BAQUFADB3MQswCQYDVQQGEwJVUzERMA8GA1UE +CBMIQ29sb3JhZG8xFDASBgNVBAcTC1dlc3RtaW5zdGVyMQ4wDAYDVQQKEwVKYXNp +ZzEMMAoGA1UECxMDQ0FTMSEwHwYDVQQDExhDQVMgVGVzdCBJbnRlcm1lZGlhdGUg +Q0EXDTExMDEyMTE2MTYyOFoXDTMyMTIxNjE2MTYyOFqgDjAMMAoGA1UdFAQDAgEB +MA0GCSqGSIb3DQEBBQUAA4IBAQB6TYyMmFZpPbqRyby1e/+Y+wZCyVMCxTnDSc7+ +Z4LiXQ0gN8+OOqSxHRAbX1DmE1Sn268yLpdJOmHWvBN7eHf+TTKpnsjDducL6BCT +Cf57zgTGA3Nrxg2tg2CVMscW+wD3T0/UWYwEnMm3hPxZft9FUAGX9yszYZLt+J2s +gTL2cKt1Fgsfg7+Uzdf714694j25bY00Z6gQDE1Y4ZMa3qapKGjvnjqqH0DqnSHB +KKYtGE08s7+R6CTFKTbDLpQbXPondDulJlVWM12w+6KDPkfiSm79QhOtePOaNru8 +pZP0xTRsh2cWsPkMZWsBm35IvPGLi668Er5z2YdnOdk89fYV +-----END X509 CRL----- diff --git a/cas-server-support-x509/src/test/resources/intermediateCA.crt b/cas-server-support-x509/src/test/resources/intermediateCA.crt new file mode 100644 index 000000000000..8369b95cca19 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/intermediateCA.crt @@ -0,0 +1,84 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3237 (0xca5) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test Root CA + Validity + Not Before: Jan 19 16:47:44 2011 GMT + Not After : Dec 14 16:47:44 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test Intermediate CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:dd:46:76:c1:1d:68:29:5f:1e:90:93:66:88:75: + ca:21:91:e8:a6:cc:f3:b0:58:30:d2:94:2d:62:51: + 1d:63:e3:7b:7b:e7:f2:3a:bc:c4:c9:e5:48:b0:46: + cd:7b:c9:f7:27:bf:5f:e9:01:57:95:cb:c5:b1:e1: + 3c:85:b2:61:f3:0f:c9:fd:e0:8f:0a:fe:3a:f5:2a: + 27:09:22:44:dc:7c:c6:49:1d:f7:af:24:27:2c:dd: + 1d:77:81:dd:46:08:75:f5:3a:d4:7d:c7:92:b0:fb: + 36:02:04:6d:10:b5:43:e2:93:c4:60:19:40:ca:35: + f5:fb:60:e8:4c:d0:25:46:33:6c:b2:f2:8d:13:ef: + 7c:55:d9:58:78:4c:e6:7f:ef:7f:0a:30:44:76:3e: + 50:69:a4:cd:05:33:fa:dd:9a:1c:cb:60:46:46:58: + be:9a:cc:dd:94:63:09:d7:cb:ee:40:9c:a0:b0:37: + 42:3f:58:a8:9d:f2:07:d0:24:fc:96:5b:de:a1:e4: + ad:09:c3:c3:81:41:6b:41:95:07:e8:c7:6f:4a:01: + 77:e4:ac:2d:13:5a:f9:a5:44:5a:50:58:c4:83:7c: + e0:80:95:11:5c:42:ad:24:76:58:2b:e5:1d:1c:f9: + a5:69:34:23:07:6b:bf:3b:82:e7:62:5c:dc:9d:ae: + 93:db + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 12:38:AD:05:C8:59:B1:10:9F:FF:A8:6B:2E:CE:03:52:FF:59:D8:7F + X509v3 Authority Key Identifier: + keyid:F1:1A:2E:93:C7:42:81:1B:1B:2B:1B:1B:65:28:DD:AF:8D:EA:43:F2 + DirName:/C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=CAS Test Root CA + serial:0C:A5 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 62:1f:fa:01:79:8d:f8:f1:52:a6:4e:fa:50:4d:0b:c5:34:46: + ee:a4:17:51:7a:1f:96:4d:d5:2b:a9:40:0d:0f:fb:a0:1a:a9: + 39:88:95:36:14:8d:6c:3b:bb:e3:9f:5b:39:8c:fc:c0:49:0c: + e4:32:13:cf:b0:c9:1b:75:4c:b2:5b:c0:4e:0d:2b:d0:71:fc: + 01:02:eb:a9:61:37:75:1a:1f:69:61:53:b5:60:9f:84:73:dd: + 86:05:21:f3:03:38:81:0b:49:81:ba:1d:2e:86:4b:f7:40:0e: + 3f:4c:a1:2e:9f:cd:d0:1d:a0:9b:b8:42:fc:86:48:1f:a6:8a: + 28:50:d0:dc:ed:06:42:98:05:94:83:02:10:e7:e5:3e:89:ac: + 36:9d:ad:7b:a6:14:b2:fc:79:6f:1b:fe:7d:49:b4:46:0e:cb: + 66:ac:54:4b:45:88:44:f8:d9:ee:8d:d9:1d:a4:40:58:35:b6: + f9:8f:d5:ea:f8:72:67:4d:77:f5:f4:2f:47:1f:30:d1:73:8a: + 15:ef:e4:b0:b3:50:8e:21:ec:eb:75:65:4f:37:2b:d5:e1:21: + 33:76:7e:a6:87:bd:86:89:2a:c9:7a:f5:d7:0e:e0:db:a3:81: + 1b:3d:10:02:ce:13:88:47:d5:f5:f8:a4:c5:e1:bf:4b:c4:d7: + 0c:bc:11:a4 +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgICDKUwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgUm9vdCBD +QTAeFw0xMTAxMTkxNjQ3NDRaFw0zMjEyMTQxNjQ3NDRaMHcxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxITAfBgNVBAMTGENBUyBUZXN0IEludGVy +bWVkaWF0ZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN1GdsEd +aClfHpCTZoh1yiGR6KbM87BYMNKULWJRHWPje3vn8jq8xMnlSLBGzXvJ9ye/X+kB +V5XLxbHhPIWyYfMPyf3gjwr+OvUqJwkiRNx8xkkd968kJyzdHXeB3UYIdfU61H3H +krD7NgIEbRC1Q+KTxGAZQMo19ftg6EzQJUYzbLLyjRPvfFXZWHhM5n/vfwowRHY+ +UGmkzQUz+t2aHMtgRkZYvprM3ZRjCdfL7kCcoLA3Qj9YqJ3yB9Ak/JZb3qHkrQnD +w4FBa0GVB+jHb0oBd+SsLRNa+aVEWlBYxIN84ICVEVxCrSR2WCvlHRz5pWk0Iwdr +vzuC52Jc3J2uk9sCAwEAAaOBzTCByjAdBgNVHQ4EFgQUEjitBchZsRCf/6hrLs4D +Uv9Z2H8wgZoGA1UdIwSBkjCBj4AU8Rouk8dCgRsbKxsbZSjdr43qQ/Khc6RxMG8x +CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1p +bnN0ZXIxDjAMBgNVBAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxGTAXBgNVBAMTEENB +UyBUZXN0IFJvb3QgQ0GCAgylMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBAGIf+gF5jfjxUqZO+lBNC8U0Ru6kF1F6H5ZN1SupQA0P+6AaqTmIlTYUjWw7 +u+OfWzmM/MBJDOQyE8+wyRt1TLJbwE4NK9Bx/AEC66lhN3UaH2lhU7Vgn4Rz3YYF +IfMDOIELSYG6HS6GS/dADj9MoS6fzdAdoJu4QvyGSB+miihQ0NztBkKYBZSDAhDn +5T6JrDadrXumFLL8eW8b/n1JtEYOy2asVEtFiET42e6N2R2kQFg1tvmP1er4cmdN +d/X0L0cfMNFzihXv5LCzUI4h7Ot1ZU83K9XhITN2fqaHvYaJKsl69dcO4NujgRs9 +EALOE4hH1fX4pMXhv0vE1wy8EaQ= +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/src/test/resources/ldap-crl.crt b/cas-server-support-x509/src/test/resources/ldap-crl.crt new file mode 100644 index 000000000000..2f1fcbd048b5 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/ldap-crl.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDkTCCAnmgAwIBAgIBATANBgkqhkiG9w0BAQUFADB2MQswCQYDVQQGEwJVUzEN +MAsGA1UECBMEdGVzdDENMAsGA1UEBxMEdGVzdDENMAsGA1UEChMEdGVzdDENMAsG +A1UECxMEdGVzdDENMAsGA1UEAxMEdGVzdDEcMBoGCSqGSIb3DQEJARYNdGVzdEB0 +ZXN0LmVkdTAeFw0xNTA0MTQxNTM3MDBaFw0xNjA0MTQxNTM3MDBaMHYxCzAJBgNV +BAYTAlVTMQ0wCwYDVQQIEwR0ZXN0MQ0wCwYDVQQHEwR0ZXN0MQ0wCwYDVQQKEwR0 +ZXN0MQ0wCwYDVQQLEwR0ZXN0MQ0wCwYDVQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkB +Fg10ZXN0QHRlc3QuZWR1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +w4d29Tqb3MK/6TEQpRAopvE2NDaRv1jDe1Rdt6LPox8xdTZJoQICWd/kQ2DRsfJD +GtElbKruQvbN5LpwwAGGl7QKoixlyL9Ka3C4iVSk1L3fORElxl7JqRgA5yZT/mds +shIUHv0ALVj7wz+6xcdZJbkhoKuw88LlaOHT4LDvQQjR2KMQRE7dp0KHwbZqfv9B +yshyk45OacuOX0ZDhTkcNctQhFHUF9E0+whkfoOoy5TxgPllCNcMJMhOgk+6oNjW +d56BbIaRa15+P9BjaMy1HyvEF8NqFuDRSeCfmtL712lyGu426scC798Ni+pgP4RK +kFFe1ljoHxtGIVY1O6Dm+QIDAQABoyowKDAmBgNVHR8EHzAdMBugGaAXhhVsZGFw +Oi8vbG9jYWxob3N0OjEzODkwDQYJKoZIhvcNAQEFBQADggEBAJzIkDqwdxrd8Sdi +4AIJAYmHZwcgGoszeQMXCiPR9ruZUvm6CBtMugSIqvEfqb7RSv/h9chq6kO0Yan3 +2LcxVeiQ+IJ4Oeq0ACMDWHvbW31bG6466E0+B1085vTs/sjIwRQOMdYcvit4m/zy +zt8NTQJ6SS1q51IJQw6LTXA9o14K9ym/0ngduWi0mTrn/pCFYaL+3NUBhCL30mDr +r3/nJJaZHSiztMR5uTceY1k5R0f8nelY9x9mfKyPk4BvRVzNI6KmY+otHMIQ80CH +va58QIklTD92chUrYCL98eUBa9YxHw+ySsoppoe7nhlGbMZhT7Z28KZVr1J0MNBR +t193T4Y= +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/src/test/resources/ldap.properties b/cas-server-support-x509/src/test/resources/ldap.properties new file mode 100644 index 000000000000..d8362e2897f4 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/ldap.properties @@ -0,0 +1,64 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +#======================================== +# General properties +#======================================== +ldap.url=ldap://localhost:1389 + +# Directory root DN +ldap.rootDn=dc=example,dc=org + +# Base DN of users to be authenticated +ldap.baseDn=ou=people,dc=example,dc=org + +# LDAP connection timeout in milliseconds +ldap.connectTimeout=3000 + +# Manager credential DN +ldap.managerDn=cn=Directory Manager,dc=example,dc=org + +# Manager credential password +ldap.managerPassword=Password + +#======================================== +# LDAP connection pool configuration +#======================================== +ldap.pool.minSize=1 +ldap.pool.maxSize=10 +ldap.pool.validateOnCheckout=false +ldap.pool.validatePeriodically=true + +# Amount of time in milliseconds to block on pool exhausted condition +# before giving up. +ldap.pool.blockWaitTime=3000 + +# Frequency of connection validation in seconds +# Only applies if validatePeriodically=true +ldap.pool.validatePeriod=300 + +# Attempt to prune connections every N seconds +ldap.pool.prunePeriod=300 + +# Maximum amount of time an idle connection is allowed to be in +# pool before it is liable to be removed/destroyed +ldap.pool.idleTime=600 + +ldap.searchfilter.cert=CN=x509 + diff --git a/cas-server-support-x509/src/test/resources/ldif/users-x509.ldif b/cas-server-support-x509/src/test/resources/ldif/users-x509.ldif new file mode 100644 index 000000000000..909cc510d4f2 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/ldif/users-x509.ldif @@ -0,0 +1,57 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +dn: CN=x509,ou=people,dc=example,dc=org +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: ad +objectClass: inetOrgPerson +objectClass: cRLDistributionPoint +cn: Misagh Moayyed +sn: Moayyed +givenName: Misagh +displayName: Misagh Moayyed +sAMAccountName: mmoayyed +mail: mmoayyed@example.org +userPrincipalName: mmoayyed@example.org +userAccountControl: 66048 +distinguishedName: CN=mmoayyed,ou=people,dc=example,dc=org +userPassword: misagh +unicodePwd: ignoredForTests + diff --git a/cas-server-support-x509/src/test/resources/log4j2.xml b/cas-server-support-x509/src/test/resources/log4j2.xml new file mode 100644 index 000000000000..113b216a3685 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/log4j2.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-support-x509/src/test/resources/rootCA-valid.crl b/cas-server-support-x509/src/test/resources/rootCA-valid.crl new file mode 100644 index 000000000000..87f9ed38c153 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/rootCA-valid.crl @@ -0,0 +1,12 @@ +-----BEGIN X509 CRL----- +MIIByDCBsQIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJVUzERMA8GA1UE +CBMIQ29sb3JhZG8xFDASBgNVBAcTC1dlc3RtaW5zdGVyMQ4wDAYDVQQKEwVKYXNp +ZzEMMAoGA1UECxMDQ0FTMRkwFwYDVQQDExBDQVMgVGVzdCBSb290IENBFw0xMTAx +MjExNjE3MDdaFw0zMjEyMTYxNjE3MDdaoA4wDDAKBgNVHRQEAwIBATANBgkqhkiG +9w0BAQUFAAOCAQEAQ+yjVZ1lKLBCfD4svjmq+9siueymLZWFW2JFXLQeGQvvLgfO +LQYi2+51RQD+AljjqPj50++QN5kBoovNHzfh7TCtnfWjaoSRAwXfVvtL/tG6peMF +DgNSsufZAtfrR/CMxv6oyhfMWLu1N36gBY0YuB+mh2gqAkeKHA3179A66lchMRnc +BS0UWQZEJMwnGBn+5Sfc1VRDiPPGkceHH8M/z5lAD7R6yg/KCVKHviLm1C/16+ws +wb2fRyY1CbFx1Sg+Dsr3BgUznMfMUtmHP2yAdmCUdYIvNPrBWajJm5UUUkSMNCYs +8MWVtZ6Soc8xZa0s1R6FZUrtzrpCoQUAq8XOIw== +-----END X509 CRL----- diff --git a/cas-server-support-x509/src/test/resources/rootCA.crt b/cas-server-support-x509/src/test/resources/rootCA.crt new file mode 100644 index 000000000000..91ca4a91ff33 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/rootCA.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEKDCCAxCgAwIBAgICDKUwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgUm9vdCBD +QTAeFw0xMDEyMDMxODUyMDBaFw0zODA0MjAxODUyMDBaMG8xCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxGTAXBgNVBAMTEENBUyBUZXN0IFJvb3Qg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCe6snxpRVgR8OgTwve +Lb326AeEpplLEDeyR9IuawwfIovCFcWhyuH5CJq3KxlwEKbYXr/Dd1xRcigZDMJX +dqJ/Ke4Gsq2fNyur1pk0LUOTGJC7sVeSdbfXEVR3hDns27q/avinvYb1rwfr6Q5e +mJWrqE4J30Ff0AZ1gGXUF8hqYsxocMRQ1hezdSH4B+ukJY48FOxfEoxcTsqSNSHg +yhbVuZ7pJXh/JcCCloNPLvEcDsxwKcXNGdMrO6PIF7DuIFIMMERyhzDY/ZuKBZJv +bhY0Lz2RyMZh2rA6kw7YQLpTzxqakz+1WSixKY++fmeG6VXK/9y4xi4YpfQ6rq2l +Y5vvAgMBAAGjgc0wgcowHQYDVR0OBBYEFPEaLpPHQoEbGysbG2Uo3a+N6kPyMIGa +BgNVHSMEgZIwgY+AFPEaLpPHQoEbGysbG2Uo3a+N6kPyoXOkcTBvMQswCQYDVQQG +EwJVUzERMA8GA1UECBMIQ29sb3JhZG8xFDASBgNVBAcTC1dlc3RtaW5zdGVyMQ4w +DAYDVQQKEwVKYXNpZzEMMAoGA1UECxMDQ0FTMRkwFwYDVQQDExBDQVMgVGVzdCBS +b290IENBggIMpTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAmDP2G +r/Vgowgt0ncwkDc5hiyd22cavavZ2n8+3mBaun2vc7ytbdhOMWSVrO5RzWj+IdGx +2MuTmtS3uoAMxyKLYlSD1DyOZpBeFj0+YCMeNr42bE7zhQyogMockbMAxgC0oAtL +mL1G+zVEnva76HLzkes2faP65u36LYwiZc5Cld3hHI8R1F18Vj9us+XAHMZlK7fS +c6d7FpR8SOUP0LyRYDaD26DuU3TjoufIpQFuMpzY5Z5LK5zseVnR8n/Ue4UzBJCh +1AAhMOyhfBB8M5Ypkn+csHcdd/Jd/hMy0N8BniGHRFug0RMlgY34YNK1sLo59qUV +y9ThiKbmq1N+znoL +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/src/test/resources/snake-oil.crt b/cas-server-support-x509/src/test/resources/snake-oil.crt new file mode 100644 index 000000000000..aac2f8619326 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/snake-oil.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIESzCCAzOgAwIBAgIJAL/ykk7Ygr6AMA0GCSqGSIb3DQEBBQUAMHYxCzAJBgNV +BAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzES +MBAGA1UEChMJU25ha2UgT2lsMRIwEAYDVQQLEwlFeHRvcnRpb24xFzAVBgNVBAMT +DkxpdHRsZSBCbyBQZWVwMB4XDTExMDEyNDE2NTYzOFoXDTM4MDYxMTE2NTYzOFow +djELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYDVQQHEwpCbGFj +a3NidXJnMRIwEAYDVQQKEwlTbmFrZSBPaWwxEjAQBgNVBAsTCUV4dG9ydGlvbjEX +MBUGA1UEAxMOTGl0dGxlIEJvIFBlZXAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQC5ZeXa4/RUO6rJd7i7hcNS/cxYn1XKPHLu2GgEijNXPlLiRAA1Kuac +2vH5a6fNT5YdSBh3a89ajSfzcGM66WqMBHoSa21bggKbQva41NRHdvikFe+g/2XN +IJiXIUgnD/KUijOBfvcZBGpkD3o2tGk9voUcXPTAJjdiRPS0I+NsvUBqNzM9AfV5 +66Od14M8nGZxnUeEJRr11QUlCv1+n6GPZo+wQ5KOFllGE5j2zLldhuQyCdiFmKsR +zXzhlcRKRktRTg2X6srZSJHZ8Loe9d2DvfHzyLqXMf8G0PB+mVvxOU2nfRAM9+bD +zLMHmTIXcv33c7e8l/RZFS+C8aQOcI83AgMBAAGjgdswgdgwHQYDVR0OBBYEFPb9 +AXJySHWKiWMG95LePh/OBNhpMIGoBgNVHSMEgaAwgZ2AFPb9AXJySHWKiWMG95Le +Ph/OBNhpoXqkeDB2MQswCQYDVQQGEwJVUzERMA8GA1UECBMIVmlyZ2luaWExEzAR +BgNVBAcTCkJsYWNrc2J1cmcxEjAQBgNVBAoTCVNuYWtlIE9pbDESMBAGA1UECxMJ +RXh0b3J0aW9uMRcwFQYDVQQDEw5MaXR0bGUgQm8gUGVlcIIJAL/ykk7Ygr6AMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAGq64C9eV4lkOgyF3HpKVrae +jbNmgLuy9EzGju2M6Jb16CQfzSJWEnEIiY9YlzMmY3Y7TNjUq+w8aXxagSkun8ue +g2LhLz1avIm8EUpLTrijlmkU3bgcMfonZIl5r3VVKc2REaAd9Hha5rKD1/4E6Ff2 +pFG6p+pySiGJQdEG9hffwP4OM1cVuQJF33HPNtyQdMBI3/PiUX6lSk43IIuGkJ0i +62cxzsnNYzgDd32jSXKo9JJML6HJMxpmAYn5KceNN/NflUhpj6pu7zp4D5gXOYAX +F401MK2Z2j+xUsRBIv201csRkXuOSy5i0Q2W8cZ4xr86RGZ/8ioxbyckrbKEArE= +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/src/test/resources/test ca.crl b/cas-server-support-x509/src/test/resources/test ca.crl new file mode 100644 index 000000000000..50615d7e7031 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/test ca.crl @@ -0,0 +1,16 @@ +-----BEGIN X509 CRL----- +MIICcjCCAVoCAQEwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMxETAPBgNV +BAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UEChMFSmFz +aWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBDQRcNMTEw +MTMxMTcwNDUyWhcNMzIxMjI2MTcwNDUyWjCBpjA7AgIMpxcNMTEwMTE5MTg0MTM2 +WjAmMAoGA1UdFQQDCgEBMBgGA1UdGAQRGA8yMDEwMDExOTE5MDAwMFowIQICDKsX +DTExMDEyNjE2MTA1NlowDDAKBgNVHRUEAwoBATAhAgIMrBcNMTEwMTI2MTYyMTUz +WjAMMAoGA1UdFQQDCgEBMCECAgytFw0xMTAxMzExNzA0MTJaMAwwCgYDVR0VBAMK +AQGgDjAMMAoGA1UdFAQDAgEKMA0GCSqGSIb3DQEBBQUAA4IBAQB/7omhF61Z3FQT +usNs6lRrwvR3QTOKcBxl5fkTjYcuRUgDmqeOi9NXTq6Nynx0lRxzA0I1X7d380px +Q8F/Pyl/UrgcJ0wK+oR2q2a+rzGxkAHEpfQmFMsOyQCsp6eRGNhmeSa7APj/rqll +DlR13bPW0w5SxLZls2yOoYkXAdNZmq9MYgAq7rQNWAmr4dKr1kejb6kK3B3FDuxt +XrfPyDf+H49Ynb/3jhmsZ6dUst3S9wFLAinskChFmkNaelNsyTD6Gx6xCsL5FKgh +9dqPOgxjJFPrcFnqJ1bDsQ4m0totiHpZU4+Aid4+majeh3o4o3vy5hcrul7OY9NI +MhpDmU6F +-----END X509 CRL----- diff --git a/cas-server-support-x509/src/test/resources/user-expired.crt b/cas-server-support-x509/src/test/resources/user-expired.crt new file mode 100644 index 000000000000..a8d1e4707cbf --- /dev/null +++ b/cas-server-support-x509/src/test/resources/user-expired.crt @@ -0,0 +1,82 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3238 (0xca6) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Validity + Not Before: Jan 19 18:38:13 2011 GMT + Not After : Jan 19 19:00:00 2010 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=Handyman Bob + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:7b:c9:05:71:72:77:e4:7a:9b:3f:5f:5b:5d: + 2c:84:65:8a:3e:45:d5:29:71:cd:a0:01:46:f0:76: + a3:17:52:14:70:1c:68:11:b5:1e:de:4f:2a:c5:72: + d9:c5:dd:f3:e2:f3:5b:4b:c0:f1:92:29:3b:8a:1f: + 0a:56:39:fe:02:28:cc:32:76:81:d8:db:3e:21:62: + dd:89:24:07:0b:d9:3a:ae:19:78:61:a0:b3:d5:ed: + e0:00:02:87:cb:ff:79:e6:e9:b0:b7:38:a6:db:78: + e1:cc:60:dd:6b:d7:4a:97:80:81:76:3c:6d:91:e4: + de:09:cf:a0:e6:75:29:ba:c6:d1:9c:7d:90:5d:da: + af:e4:a0:9e:fa:7b:2a:9e:da:e4:d4:64:1e:4c:25: + 77:f0:3c:f5:ea:7a:8c:e8:9f:ed:ab:66:ee:c3:c7: + 72:8c:25:30:82:7e:3f:28:22:a1:b5:6a:1d:64:8f: + 8f:45:62:33:2e:f6:bb:0b:88:93:b7:0a:45:55:d6: + 2d:84:72:62:5d:26:ec:8b:db:58:4f:b8:23:48:ed: + be:f4:55:51:8c:5b:25:2e:cf:16:12:30:d4:05:68: + e4:78:db:ab:bb:66:89:19:70:d0:a9:09:48:44:fb: + f4:7a:6c:12:55:fd:14:13:da:1f:14:c4:de:83:12: + c6:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 27:3E:80:D5:75:E0:F6:D1:53:E0:68:92:6F:67:05:DB:36:08:95:21 + X509v3 Authority Key Identifier: + keyid:19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + + Signature Algorithm: sha1WithRSAEncryption + 49:29:38:5f:a4:b4:08:06:70:4f:e7:de:35:f5:4f:74:b3:50: + a6:ca:3e:7c:67:f6:1e:65:ca:e2:9a:32:3c:58:28:10:33:36: + 18:19:19:05:38:45:16:53:c2:54:bd:2c:3f:82:c6:59:f6:ec: + c8:13:4e:b3:29:8e:fd:c0:ee:0f:68:26:2d:f6:35:97:a6:0f: + 6c:7f:e9:03:a1:bb:5c:8a:52:92:90:7f:40:a5:ff:b4:99:81: + 9d:c3:71:f6:fd:2f:c4:a2:11:88:fa:79:22:3f:a6:f7:2a:62: + fa:db:6e:43:a9:30:2d:96:c3:2d:3a:be:79:f7:ae:90:22:23: + e8:7b:24:5f:63:00:38:30:4b:db:22:d8:84:48:e1:2a:5f:d5: + 5f:8b:3c:c0:9d:8b:29:45:2d:b5:33:7f:e6:46:38:ea:f4:65: + f3:d7:29:0a:f9:23:62:d7:ad:93:ae:04:fb:c1:fa:5b:50:80: + ba:2d:05:d0:81:47:d1:c6:c0:4e:85:74:b0:9e:14:ec:73:79: + 3d:75:f5:bb:2b:dd:16:5c:8b:5f:65:c0:7b:c3:25:83:c4:3b: + b5:eb:c3:72:53:bd:a0:bf:0f:a7:3e:2c:64:21:2f:dc:c2:5c: + b6:eb:ee:10:37:d2:3a:19:96:b1:11:41:06:77:0d:eb:3b:b5: + 2c:26:ce:67 +-----BEGIN CERTIFICATE----- +MIID0TCCArmgAwIBAgICDKYwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMTkxODM4MTNaFw0xMDAxMTkxOTAwMDBaMGsxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxFTATBgNVBAMTDEhhbmR5bWFuIEJvYjCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMt7yQVxcnfkeps/X1tdLIRl +ij5F1SlxzaABRvB2oxdSFHAcaBG1Ht5PKsVy2cXd8+LzW0vA8ZIpO4ofClY5/gIo +zDJ2gdjbPiFi3YkkBwvZOq4ZeGGgs9Xt4AACh8v/eebpsLc4ptt44cxg3WvXSpeA +gXY8bZHk3gnPoOZ1KbrG0Zx9kF3ar+Sgnvp7Kp7a5NRkHkwld/A89ep6jOif7atm +7sPHcowlMIJ+PygiobVqHWSPj0ViMy72uwuIk7cKRVXWLYRyYl0m7IvbWE+4I0jt +vvRVUYxbJS7PFhIw1AVo5Hjbq7tmiRlw0KkJSET79HpsElX9FBPaHxTE3oMSxgEC +AwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5l +cmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFCc+gNV14PbRU+Bokm9nBds2CJUh +MB8GA1UdIwQYMBaAFBkbeI2TYrJJZpBVblCz0K9h6znzMA0GCSqGSIb3DQEBBQUA +A4IBAQBJKThfpLQIBnBP59419U90s1Cmyj58Z/YeZcrimjI8WCgQMzYYGRkFOEUW +U8JUvSw/gsZZ9uzIE06zKY79wO4PaCYt9jWXpg9sf+kDobtcilKSkH9Apf+0mYGd +w3H2/S/EohGI+nkiP6b3KmL6225DqTAtlsMtOr55966QIiPoeyRfYwA4MEvbItiE +SOEqX9VfizzAnYspRS21M3/mRjjq9GXz1ykK+SNi162TrgT7wfpbUIC6LQXQgUfR +xsBOhXSwnhTsc3k9dfW7K90WXItfZcB7wyWDxDu168NyU72gvw+nPixkIS/cwly2 +6+4QN9I6GZaxEUEGdw3rO7UsJs5n +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/src/test/resources/user-invalid-keyUsage.crt b/cas-server-support-x509/src/test/resources/user-invalid-keyUsage.crt new file mode 100644 index 000000000000..b85cabe7ef66 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/user-invalid-keyUsage.crt @@ -0,0 +1,84 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3241 (0xca9) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Validity + Not Before: Jan 24 19:00:10 2011 GMT + Not After : Dec 19 19:00:10 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=Malificent + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:7b:c9:05:71:72:77:e4:7a:9b:3f:5f:5b:5d: + 2c:84:65:8a:3e:45:d5:29:71:cd:a0:01:46:f0:76: + a3:17:52:14:70:1c:68:11:b5:1e:de:4f:2a:c5:72: + d9:c5:dd:f3:e2:f3:5b:4b:c0:f1:92:29:3b:8a:1f: + 0a:56:39:fe:02:28:cc:32:76:81:d8:db:3e:21:62: + dd:89:24:07:0b:d9:3a:ae:19:78:61:a0:b3:d5:ed: + e0:00:02:87:cb:ff:79:e6:e9:b0:b7:38:a6:db:78: + e1:cc:60:dd:6b:d7:4a:97:80:81:76:3c:6d:91:e4: + de:09:cf:a0:e6:75:29:ba:c6:d1:9c:7d:90:5d:da: + af:e4:a0:9e:fa:7b:2a:9e:da:e4:d4:64:1e:4c:25: + 77:f0:3c:f5:ea:7a:8c:e8:9f:ed:ab:66:ee:c3:c7: + 72:8c:25:30:82:7e:3f:28:22:a1:b5:6a:1d:64:8f: + 8f:45:62:33:2e:f6:bb:0b:88:93:b7:0a:45:55:d6: + 2d:84:72:62:5d:26:ec:8b:db:58:4f:b8:23:48:ed: + be:f4:55:51:8c:5b:25:2e:cf:16:12:30:d4:05:68: + e4:78:db:ab:bb:66:89:19:70:d0:a9:09:48:44:fb: + f4:7a:6c:12:55:fd:14:13:da:1f:14:c4:de:83:12: + c6:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Non Repudiation + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 27:3E:80:D5:75:E0:F6:D1:53:E0:68:92:6F:67:05:DB:36:08:95:21 + X509v3 Authority Key Identifier: + keyid:19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + + Signature Algorithm: sha1WithRSAEncryption + 79:7d:9c:74:de:69:3e:3b:83:a7:a6:b0:4c:c0:39:49:9d:b7: + 1c:03:a2:d2:ae:61:75:d6:7d:2f:75:37:ec:bd:ca:eb:eb:62: + ec:23:62:c7:11:e8:7a:a5:46:f8:9f:a4:ad:75:b7:1f:80:08: + da:d6:dc:ff:48:b5:7e:7d:de:94:d7:c3:d5:36:a0:a7:3a:33: + 6c:11:da:3c:33:59:95:ab:e1:52:f4:de:a9:37:33:b1:1f:ff: + ae:3b:2a:18:d4:bf:42:a1:86:59:f1:be:a0:62:df:47:39:e0: + 24:91:80:a6:3f:43:7e:6e:e9:af:a8:98:67:24:92:fb:66:06: + e5:26:c6:85:f1:fd:c5:d6:92:cf:e6:6e:a2:8a:b4:3e:22:6b: + a4:20:e3:13:eb:ee:d4:1c:b1:e9:21:70:30:20:af:c8:87:1f: + 37:fa:81:7e:3a:09:23:e9:43:17:a5:4b:40:17:f7:f5:67:2d: + a2:66:af:48:8c:bc:84:ad:99:8c:97:0f:e2:ac:97:96:5d:73: + 9b:76:99:8d:62:b8:eb:63:d9:94:79:14:d7:8c:16:91:ed:a1: + 5e:e6:0c:b9:8f:75:39:a0:1a:d6:69:0e:27:46:d7:8e:1c:71: + 7b:b6:4c:7f:df:83:34:d9:ac:c1:3d:45:a1:4f:c9:9e:06:46: + 27:d3:bc:b3 +-----BEGIN CERTIFICATE----- +MIID3jCCAsagAwIBAgICDKkwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMjQxOTAwMTBaFw0zMjEyMTkxOTAwMTBaMGkxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxEzARBgNVBAMTCk1hbGlmaWNlbnQwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLe8kFcXJ35HqbP19bXSyEZYo+ +RdUpcc2gAUbwdqMXUhRwHGgRtR7eTyrFctnF3fPi81tLwPGSKTuKHwpWOf4CKMwy +doHY2z4hYt2JJAcL2TquGXhhoLPV7eAAAofL/3nm6bC3OKbbeOHMYN1r10qXgIF2 +PG2R5N4Jz6DmdSm6xtGcfZBd2q/koJ76eyqe2uTUZB5MJXfwPPXqeozon+2rZu7D +x3KMJTCCfj8oIqG1ah1kj49FYjMu9rsLiJO3CkVV1i2EcmJdJuyL21hPuCNI7b70 +VVGMWyUuzxYSMNQFaOR426u7ZokZcNCpCUhE+/R6bBJV/RQT2h8UxN6DEsYBAgMB +AAGjgYkwgYYwCQYDVR0TBAIwADALBgNVHQ8EBAMCBkAwLAYJYIZIAYb4QgENBB8W +HU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQnPoDVdeD2 +0VPgaJJvZwXbNgiVITAfBgNVHSMEGDAWgBQZG3iNk2KySWaQVW5Qs9CvYes58zAN +BgkqhkiG9w0BAQUFAAOCAQEAeX2cdN5pPjuDp6awTMA5SZ23HAOi0q5hddZ9L3U3 +7L3K6+ti7CNixxHoeqVG+J+krXW3H4AI2tbc/0i1fn3elNfD1TagpzozbBHaPDNZ +lavhUvTeqTczsR//rjsqGNS/QqGGWfG+oGLfRzngJJGApj9Dfm7pr6iYZySS+2YG +5SbGhfH9xdaSz+Zuooq0PiJrpCDjE+vu1Byx6SFwMCCvyIcfN/qBfjoJI+lDF6VL +QBf39WctomavSIy8hK2ZjJcP4qyXll1zm3aZjWK462PZlHkU14wWke2hXuYMuY91 +OaAa1mkOJ0bXjhxxe7ZMf9+DNNmswT1FoU/JngZGJ9O8sw== +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/src/test/resources/user-revoked-distcrl.crt b/cas-server-support-x509/src/test/resources/user-revoked-distcrl.crt new file mode 100644 index 000000000000..ce100b3c66df --- /dev/null +++ b/cas-server-support-x509/src/test/resources/user-revoked-distcrl.crt @@ -0,0 +1,86 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3244 (0xcac) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Validity + Not Before: Jan 26 16:21:27 2011 GMT + Not After : Dec 21 16:21:27 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=Marauder + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:7b:c9:05:71:72:77:e4:7a:9b:3f:5f:5b:5d: + 2c:84:65:8a:3e:45:d5:29:71:cd:a0:01:46:f0:76: + a3:17:52:14:70:1c:68:11:b5:1e:de:4f:2a:c5:72: + d9:c5:dd:f3:e2:f3:5b:4b:c0:f1:92:29:3b:8a:1f: + 0a:56:39:fe:02:28:cc:32:76:81:d8:db:3e:21:62: + dd:89:24:07:0b:d9:3a:ae:19:78:61:a0:b3:d5:ed: + e0:00:02:87:cb:ff:79:e6:e9:b0:b7:38:a6:db:78: + e1:cc:60:dd:6b:d7:4a:97:80:81:76:3c:6d:91:e4: + de:09:cf:a0:e6:75:29:ba:c6:d1:9c:7d:90:5d:da: + af:e4:a0:9e:fa:7b:2a:9e:da:e4:d4:64:1e:4c:25: + 77:f0:3c:f5:ea:7a:8c:e8:9f:ed:ab:66:ee:c3:c7: + 72:8c:25:30:82:7e:3f:28:22:a1:b5:6a:1d:64:8f: + 8f:45:62:33:2e:f6:bb:0b:88:93:b7:0a:45:55:d6: + 2d:84:72:62:5d:26:ec:8b:db:58:4f:b8:23:48:ed: + be:f4:55:51:8c:5b:25:2e:cf:16:12:30:d4:05:68: + e4:78:db:ab:bb:66:89:19:70:d0:a9:09:48:44:fb: + f4:7a:6c:12:55:fd:14:13:da:1f:14:c4:de:83:12: + c6:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 27:3E:80:D5:75:E0:F6:D1:53:E0:68:92:6F:67:05:DB:36:08:95:21 + X509v3 Authority Key Identifier: + keyid:19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + + X509v3 CRL Distribution Points: + URI:http://localhost:8085/ca.crl + + Signature Algorithm: sha1WithRSAEncryption + 1d:ce:c3:da:f9:52:fa:8d:cb:68:be:17:2d:3d:b2:dc:86:82: + 4a:4b:db:90:95:58:21:41:93:9f:8f:86:22:00:2c:6f:ae:29: + 27:30:cc:78:d1:79:84:27:e0:44:a4:d2:af:7c:1e:1a:71:59: + 44:b0:23:ff:73:34:09:63:78:37:57:87:62:eb:2f:4b:b7:16: + 90:f9:f8:5a:4d:e5:c3:ac:e3:04:9e:ff:0c:87:aa:e1:2b:25: + ef:2d:59:03:36:53:6b:d5:54:dc:3c:39:13:88:06:e0:58:f0: + 1b:f6:10:9e:20:19:40:bb:ac:00:15:a7:09:72:3c:65:81:d9: + 40:40:68:d4:a0:7b:f5:33:8f:fa:54:13:cf:e9:ef:f6:e4:41: + aa:41:c2:95:c2:55:9f:39:78:3f:d1:1e:66:4b:de:f7:b5:53: + 83:1b:a2:0b:2e:51:9c:d1:c4:2b:75:d8:59:dc:26:64:b1:c8: + bc:29:ff:50:ca:4e:ba:b0:c7:c5:ca:d7:97:1b:ff:11:b3:fc: + cc:9e:05:6d:c6:80:2c:33:a4:d6:38:b0:31:ae:ee:90:4c:14: + 7b:b0:63:76:b5:b8:95:de:9d:72:3b:17:f7:df:e0:b0:cf:f1: + c8:46:5b:7d:4c:06:2b:a3:42:c7:99:c8:85:9f:d8:af:21:45: + f3:ee:29:78 +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgICDKwwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMjYxNjIxMjdaFw0zMjEyMjExNjIxMjdaMGcxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxETAPBgNVBAMTCE1hcmF1ZGVyMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy3vJBXFyd+R6mz9fW10shGWKPkXV +KXHNoAFG8HajF1IUcBxoEbUe3k8qxXLZxd3z4vNbS8Dxkik7ih8KVjn+AijMMnaB +2Ns+IWLdiSQHC9k6rhl4YaCz1e3gAAKHy/955umwtzim23jhzGDda9dKl4CBdjxt +keTeCc+g5nUpusbRnH2QXdqv5KCe+nsqntrk1GQeTCV38Dz16nqM6J/tq2buw8dy +jCUwgn4/KCKhtWodZI+PRWIzLva7C4iTtwpFVdYthHJiXSbsi9tYT7gjSO2+9FVR +jFslLs8WEjDUBWjkeNuru2aJGXDQqQlIRPv0emwSVf0UE9ofFMTegxLGAQIDAQAB +o4GrMIGoMAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJh +dGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQnPoDVdeD20VPgaJJvZwXbNgiVITAf +BgNVHSMEGDAWgBQZG3iNk2KySWaQVW5Qs9CvYes58zAtBgNVHR8EJjAkMCKgIKAe +hhxodHRwOi8vbG9jYWxob3N0OjgwODUvY2EuY3JsMA0GCSqGSIb3DQEBBQUAA4IB +AQAdzsPa+VL6jctovhctPbLchoJKS9uQlVghQZOfj4YiACxvriknMMx40XmEJ+BE +pNKvfB4acVlEsCP/czQJY3g3V4di6y9LtxaQ+fhaTeXDrOMEnv8Mh6rhKyXvLVkD +NlNr1VTcPDkTiAbgWPAb9hCeIBlAu6wAFacJcjxlgdlAQGjUoHv1M4/6VBPP6e/2 +5EGqQcKVwlWfOXg/0R5mS973tVODG6ILLlGc0cQrddhZ3CZksci8Kf9Qyk66sMfF +yteXG/8Rs/zMngVtxoAsM6TWOLAxru6QTBR7sGN2tbiV3p1yOxf33+Cwz/HIRlt9 +TAYro0LHmciFn9ivIUXz7il4 +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/src/test/resources/user-revoked-distcrl2.crt b/cas-server-support-x509/src/test/resources/user-revoked-distcrl2.crt new file mode 100644 index 000000000000..7b9d8c4a57a8 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/user-revoked-distcrl2.crt @@ -0,0 +1,86 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3245 (0xcad) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Validity + Not Before: Jan 31 17:03:29 2011 GMT + Not After : Dec 26 17:03:29 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=Malverde + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:7b:c9:05:71:72:77:e4:7a:9b:3f:5f:5b:5d: + 2c:84:65:8a:3e:45:d5:29:71:cd:a0:01:46:f0:76: + a3:17:52:14:70:1c:68:11:b5:1e:de:4f:2a:c5:72: + d9:c5:dd:f3:e2:f3:5b:4b:c0:f1:92:29:3b:8a:1f: + 0a:56:39:fe:02:28:cc:32:76:81:d8:db:3e:21:62: + dd:89:24:07:0b:d9:3a:ae:19:78:61:a0:b3:d5:ed: + e0:00:02:87:cb:ff:79:e6:e9:b0:b7:38:a6:db:78: + e1:cc:60:dd:6b:d7:4a:97:80:81:76:3c:6d:91:e4: + de:09:cf:a0:e6:75:29:ba:c6:d1:9c:7d:90:5d:da: + af:e4:a0:9e:fa:7b:2a:9e:da:e4:d4:64:1e:4c:25: + 77:f0:3c:f5:ea:7a:8c:e8:9f:ed:ab:66:ee:c3:c7: + 72:8c:25:30:82:7e:3f:28:22:a1:b5:6a:1d:64:8f: + 8f:45:62:33:2e:f6:bb:0b:88:93:b7:0a:45:55:d6: + 2d:84:72:62:5d:26:ec:8b:db:58:4f:b8:23:48:ed: + be:f4:55:51:8c:5b:25:2e:cf:16:12:30:d4:05:68: + e4:78:db:ab:bb:66:89:19:70:d0:a9:09:48:44:fb: + f4:7a:6c:12:55:fd:14:13:da:1f:14:c4:de:83:12: + c6:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 27:3E:80:D5:75:E0:F6:D1:53:E0:68:92:6F:67:05:DB:36:08:95:21 + X509v3 Authority Key Identifier: + keyid:19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + + X509v3 CRL Distribution Points: + URI:http://localhost:8085/ca?issuer=CN=CAS Test User CA + + Signature Algorithm: sha1WithRSAEncryption + 51:8a:3e:fe:e5:b5:9a:f6:8d:82:f5:4c:0a:22:8f:5b:c9:64: + 07:c2:7a:3a:c6:6c:17:57:1b:da:f5:32:b7:83:cc:07:0e:90: + a6:ec:e0:3e:24:3a:70:51:1b:98:a0:d0:ac:32:00:f0:d5:1f: + bf:09:d9:32:9b:f1:c2:f4:9e:71:45:a8:f7:03:82:ce:e0:b9: + e6:e5:4b:85:b2:04:6d:7b:71:f2:28:3f:1a:48:b1:4c:0d:78: + 03:d4:67:15:d5:57:06:39:dd:17:a5:bd:49:10:42:c2:cc:c9: + 29:6f:96:b2:b0:00:c8:e7:5b:c2:dd:03:3d:7c:7f:06:5d:80: + f9:32:00:56:33:68:bc:9a:57:c5:ab:c4:11:29:5c:d6:e9:21: + 9b:25:30:d8:94:cd:5d:52:76:61:9c:cb:8c:dd:ee:5a:69:26: + a3:1f:8c:55:c0:10:e3:77:4c:82:79:a4:7c:c5:1e:b5:b6:d1: + 8c:6a:0e:33:ce:43:42:c1:0b:2e:7c:72:05:78:10:d7:7f:9c: + 5f:6d:16:be:fa:18:51:8a:5d:15:39:2f:a8:ed:39:51:73:1e: + e3:39:a9:d5:54:9a:fc:6e:37:5e:af:ef:86:af:38:8a:68:32: + 49:4a:b1:1e:91:0d:51:e4:c5:5e:3d:7a:4e:54:2b:e6:58:ae: + 63:a4:d7:82 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgICDK0wDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMzExNzAzMjlaFw0zMjEyMjYxNzAzMjlaMGcxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxETAPBgNVBAMTCE1hbHZlcmRlMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy3vJBXFyd+R6mz9fW10shGWKPkXV +KXHNoAFG8HajF1IUcBxoEbUe3k8qxXLZxd3z4vNbS8Dxkik7ih8KVjn+AijMMnaB +2Ns+IWLdiSQHC9k6rhl4YaCz1e3gAAKHy/955umwtzim23jhzGDda9dKl4CBdjxt +keTeCc+g5nUpusbRnH2QXdqv5KCe+nsqntrk1GQeTCV38Dz16nqM6J/tq2buw8dy +jCUwgn4/KCKhtWodZI+PRWIzLva7C4iTtwpFVdYthHJiXSbsi9tYT7gjSO2+9FVR +jFslLs8WEjDUBWjkeNuru2aJGXDQqQlIRPv0emwSVf0UE9ofFMTegxLGAQIDAQAB +o4HCMIG/MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJh +dGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQnPoDVdeD20VPgaJJvZwXbNgiVITAf +BgNVHSMEGDAWgBQZG3iNk2KySWaQVW5Qs9CvYes58zBEBgNVHR8EPTA7MDmgN6A1 +hjNodHRwOi8vbG9jYWxob3N0OjgwODUvY2E/aXNzdWVyPUNOPUNBUyBUZXN0IFVz +ZXIgQ0EwDQYJKoZIhvcNAQEFBQADggEBAFGKPv7ltZr2jYL1TAoij1vJZAfCejrG +bBdXG9r1MreDzAcOkKbs4D4kOnBRG5ig0KwyAPDVH78J2TKb8cL0nnFFqPcDgs7g +ueblS4WyBG17cfIoPxpIsUwNeAPUZxXVVwY53RelvUkQQsLMySlvlrKwAMjnW8Ld +Az18fwZdgPkyAFYzaLyaV8WrxBEpXNbpIZslMNiUzV1SdmGcy4zd7lppJqMfjFXA +EON3TIJ5pHzFHrW20YxqDjPOQ0LBCy58cgV4ENd/nF9tFr76GFGKXRU5L6jtOVFz +HuM5qdVUmvxuN16v74avOIpoMklKsR6RDVHkxV49ek5UK+ZYrmOk14I= +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/src/test/resources/user-revoked.crt b/cas-server-support-x509/src/test/resources/user-revoked.crt new file mode 100644 index 000000000000..6d153c1e932f --- /dev/null +++ b/cas-server-support-x509/src/test/resources/user-revoked.crt @@ -0,0 +1,82 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3239 (0xca7) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Validity + Not Before: Jan 19 18:40:06 2011 GMT + Not After : Dec 14 18:40:06 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=Mallory + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:7b:c9:05:71:72:77:e4:7a:9b:3f:5f:5b:5d: + 2c:84:65:8a:3e:45:d5:29:71:cd:a0:01:46:f0:76: + a3:17:52:14:70:1c:68:11:b5:1e:de:4f:2a:c5:72: + d9:c5:dd:f3:e2:f3:5b:4b:c0:f1:92:29:3b:8a:1f: + 0a:56:39:fe:02:28:cc:32:76:81:d8:db:3e:21:62: + dd:89:24:07:0b:d9:3a:ae:19:78:61:a0:b3:d5:ed: + e0:00:02:87:cb:ff:79:e6:e9:b0:b7:38:a6:db:78: + e1:cc:60:dd:6b:d7:4a:97:80:81:76:3c:6d:91:e4: + de:09:cf:a0:e6:75:29:ba:c6:d1:9c:7d:90:5d:da: + af:e4:a0:9e:fa:7b:2a:9e:da:e4:d4:64:1e:4c:25: + 77:f0:3c:f5:ea:7a:8c:e8:9f:ed:ab:66:ee:c3:c7: + 72:8c:25:30:82:7e:3f:28:22:a1:b5:6a:1d:64:8f: + 8f:45:62:33:2e:f6:bb:0b:88:93:b7:0a:45:55:d6: + 2d:84:72:62:5d:26:ec:8b:db:58:4f:b8:23:48:ed: + be:f4:55:51:8c:5b:25:2e:cf:16:12:30:d4:05:68: + e4:78:db:ab:bb:66:89:19:70:d0:a9:09:48:44:fb: + f4:7a:6c:12:55:fd:14:13:da:1f:14:c4:de:83:12: + c6:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 27:3E:80:D5:75:E0:F6:D1:53:E0:68:92:6F:67:05:DB:36:08:95:21 + X509v3 Authority Key Identifier: + keyid:19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + + Signature Algorithm: sha1WithRSAEncryption + 61:09:50:8d:2b:92:61:e5:ac:5a:f9:cf:9f:95:fb:ae:de:de: + 59:81:9f:1a:75:52:40:f4:85:54:d2:1a:3e:d7:39:8f:d4:83: + 26:13:2d:2a:a5:dc:5a:9e:43:d6:b3:78:00:92:c8:a8:7b:4d: + 0c:38:6d:94:13:d4:87:91:1d:4e:5f:f1:2d:6b:4f:45:c8:7e: + 88:01:20:08:8c:f9:6a:4a:ed:54:58:fd:3c:f8:d7:c8:ca:98: + 7f:29:9b:2c:fb:3b:bc:7d:e0:e8:2b:df:07:02:44:7a:c0:f8: + 40:6e:9f:64:68:7f:7f:a4:b7:2a:05:75:4a:8e:b7:c5:e0:15: + 18:94:21:27:ea:b2:cd:b0:5c:4e:f0:fc:98:2c:4f:20:a1:99: + 21:0e:18:24:38:6b:6c:f8:02:45:25:98:05:8a:b9:01:c9:5f: + 21:b0:29:88:51:f0:8a:46:a9:c3:9b:6b:39:1e:c9:6f:b4:96: + 1c:15:d1:6e:df:00:36:f1:3a:35:da:e5:ea:58:15:c4:e1:1c: + b3:11:d2:b7:c1:80:23:1f:b4:3f:6c:57:1d:67:3b:cb:94:f8: + 06:7f:21:48:39:a9:4e:f7:b3:48:6c:9c:a7:d4:c6:cc:73:28: + 52:ab:06:d5:c9:89:c9:30:7d:41:01:55:ca:2a:7c:f6:aa:8f: + 28:70:f0:13 +-----BEGIN CERTIFICATE----- +MIIDzDCCArSgAwIBAgICDKcwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMTkxODQwMDZaFw0zMjEyMTQxODQwMDZaMGYxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxEDAOBgNVBAMTB01hbGxvcnkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLe8kFcXJ35HqbP19bXSyEZYo+RdUp +cc2gAUbwdqMXUhRwHGgRtR7eTyrFctnF3fPi81tLwPGSKTuKHwpWOf4CKMwydoHY +2z4hYt2JJAcL2TquGXhhoLPV7eAAAofL/3nm6bC3OKbbeOHMYN1r10qXgIF2PG2R +5N4Jz6DmdSm6xtGcfZBd2q/koJ76eyqe2uTUZB5MJXfwPPXqeozon+2rZu7Dx3KM +JTCCfj8oIqG1ah1kj49FYjMu9rsLiJO3CkVV1i2EcmJdJuyL21hPuCNI7b70VVGM +WyUuzxYSMNQFaOR426u7ZokZcNCpCUhE+/R6bBJV/RQT2h8UxN6DEsYBAgMBAAGj +ezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVk +IENlcnRpZmljYXRlMB0GA1UdDgQWBBQnPoDVdeD20VPgaJJvZwXbNgiVITAfBgNV +HSMEGDAWgBQZG3iNk2KySWaQVW5Qs9CvYes58zANBgkqhkiG9w0BAQUFAAOCAQEA +YQlQjSuSYeWsWvnPn5X7rt7eWYGfGnVSQPSFVNIaPtc5j9SDJhMtKqXcWp5D1rN4 +AJLIqHtNDDhtlBPUh5EdTl/xLWtPRch+iAEgCIz5akrtVFj9PPjXyMqYfymbLPs7 +vH3g6CvfBwJEesD4QG6fZGh/f6S3KgV1So63xeAVGJQhJ+qyzbBcTvD8mCxPIKGZ +IQ4YJDhrbPgCRSWYBYq5AclfIbApiFHwikapw5trOR7Jb7SWHBXRbt8ANvE6Ndrl +6lgVxOEcsxHSt8GAIx+0P2xXHWc7y5T4Bn8hSDmpTvezSGycp9TGzHMoUqsG1cmJ +yTB9QQFVyip89qqPKHDwEw== +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/src/test/resources/user-valid-chain.crt b/cas-server-support-x509/src/test/resources/user-valid-chain.crt new file mode 100644 index 000000000000..ac90621d37da --- /dev/null +++ b/cas-server-support-x509/src/test/resources/user-valid-chain.crt @@ -0,0 +1,98 @@ +-----BEGIN CERTIFICATE----- +MIIDyjCCArKgAwIBAgICDKUwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMTkxODI3NTdaFw0zMjEyMTQxODI3NTdaMGQxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxDjAMBgNVBAMTBUFsaWNlMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy3vJBXFyd+R6mz9fW10shGWKPkXVKXHN +oAFG8HajF1IUcBxoEbUe3k8qxXLZxd3z4vNbS8Dxkik7ih8KVjn+AijMMnaB2Ns+ +IWLdiSQHC9k6rhl4YaCz1e3gAAKHy/955umwtzim23jhzGDda9dKl4CBdjxtkeTe +Cc+g5nUpusbRnH2QXdqv5KCe+nsqntrk1GQeTCV38Dz16nqM6J/tq2buw8dyjCUw +gn4/KCKhtWodZI+PRWIzLva7C4iTtwpFVdYthHJiXSbsi9tYT7gjSO2+9FVRjFsl +Ls8WEjDUBWjkeNuru2aJGXDQqQlIRPv0emwSVf0UE9ofFMTegxLGAQIDAQABo3sw +eTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBD +ZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUJz6A1XXg9tFT4GiSb2cF2zYIlSEwHwYDVR0j +BBgwFoAUGRt4jZNisklmkFVuULPQr2HrOfMwDQYJKoZIhvcNAQEFBQADggEBAGrd +M9mP0YzaZmyG64EvuPc3i648r6JoiPduH1OQbYn/aj5eglt56VeUgjky4qulrZBb +zTq0tfOW0e6sA9BpYEJBpcdu4GidxTlLUyYncsy7cnaowr55BDWILxT4BvnyPd2a +jlqNnoxVpgKXcyoJLUtqpCsZXyh4D0D9TYwZVBocBPQV9uGKWZ+ZgKAAela1d4vI +X2ftKIo9PmkIiHGsN6SihRxuo7S/rX5D+cr4UDcQSkJTXDUEaJFe/sS0ZtJdvVBY +ul1CBJkqG6K+/6xJ9A4t4aMgIdW/9bvx4eBSPtT+8w30yi6GrhWVt2nMmsRAToXH +/IMyCfXqaJaq3zaWLTo= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgICDKUwDQYJKoZIhvcNAQEFBQAwdzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEhMB8GA1UEAxMYQ0FTIFRlc3QgSW50ZXJt +ZWRpYXRlIENBMB4XDTExMDExOTE4MjUzOVoXDTMyMTIxNDE4MjUzOVowbzELMAkG +A1UEBhMCVVMxETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3Rl +cjEOMAwGA1UEChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRl +c3QgVXNlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKudOYgJ +1uhBZSG4jKK31mypz/X++cfs47GyFgeNKo3YvHscc4UJgsmimq8Ks07cU72fJ9Ft +keR3jf68dDYmVAiB5h901lqkQX2Vk1xbFx6jO5AIli1q6ADBsTwKC5MtxAAn1a3t +0bJmRCxgsC+SJnvUiyQB1mLiSBcuPA0V/N9CWZAwhAuBhioBi1i6FQOtbgdYz6H3 +Lm7IXPKxrpA2Z9Q5lrgEtGaw90YquiFDRrn7k9E51v1AtNKd6dhP3K4UjnMFJopc +IVw5pcCaZhy4npRR7nGMmanCLjkAhQTRFKcjAE97UjVhCtIKZMkyE31GfTX2sTFI +OGBqm7W/PSM1kSECAwEAAaOBzTCByjAdBgNVHQ4EFgQUGRt4jZNisklmkFVuULPQ +r2HrOfMwgZoGA1UdIwSBkjCBj4AUEjitBchZsRCf/6hrLs4DUv9Z2H+hc6RxMG8x +CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1p +bnN0ZXIxDjAMBgNVBAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxGTAXBgNVBAMTEENB +UyBUZXN0IFJvb3QgQ0GCAgylMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBAAWVateotcyUolQaJwREVfLNL9MV7lyAmWMUtCOG/myQ3jqsJmic76tVVgB3 +TtyTMjLreNoq+4YvetDj2LPsAldgQftNslhFJpq6NZZr/wxuaNTKaXTMVeXvdAe/ +isB6YyL9Bh2TGA3CjxJFOpkPfyAjJzqLdSwmWTfWdXZLS4c0qJn565C/WuVDoPrW +Qs8exFhDwkQ6kIRcgoPba8Qh51456s5Pvijhv0jj1vsKiJK9s98sDGbSI10DiV/e +T66PBUeaqoRwtXS9GSzNygv1moJfG+KnyQ+avMJp9FzyKcvewvZjjd8VtjRkDaqj +QjLmW+tXEOMBsXi2/KL/Yj+6Q0o= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgICDKUwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgUm9vdCBD +QTAeFw0xMTAxMTkxNjQ3NDRaFw0zMjEyMTQxNjQ3NDRaMHcxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxITAfBgNVBAMTGENBUyBUZXN0IEludGVy +bWVkaWF0ZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN1GdsEd +aClfHpCTZoh1yiGR6KbM87BYMNKULWJRHWPje3vn8jq8xMnlSLBGzXvJ9ye/X+kB +V5XLxbHhPIWyYfMPyf3gjwr+OvUqJwkiRNx8xkkd968kJyzdHXeB3UYIdfU61H3H +krD7NgIEbRC1Q+KTxGAZQMo19ftg6EzQJUYzbLLyjRPvfFXZWHhM5n/vfwowRHY+ +UGmkzQUz+t2aHMtgRkZYvprM3ZRjCdfL7kCcoLA3Qj9YqJ3yB9Ak/JZb3qHkrQnD +w4FBa0GVB+jHb0oBd+SsLRNa+aVEWlBYxIN84ICVEVxCrSR2WCvlHRz5pWk0Iwdr +vzuC52Jc3J2uk9sCAwEAAaOBzTCByjAdBgNVHQ4EFgQUEjitBchZsRCf/6hrLs4D +Uv9Z2H8wgZoGA1UdIwSBkjCBj4AU8Rouk8dCgRsbKxsbZSjdr43qQ/Khc6RxMG8x +CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1p +bnN0ZXIxDjAMBgNVBAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxGTAXBgNVBAMTEENB +UyBUZXN0IFJvb3QgQ0GCAgylMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBAGIf+gF5jfjxUqZO+lBNC8U0Ru6kF1F6H5ZN1SupQA0P+6AaqTmIlTYUjWw7 +u+OfWzmM/MBJDOQyE8+wyRt1TLJbwE4NK9Bx/AEC66lhN3UaH2lhU7Vgn4Rz3YYF +IfMDOIELSYG6HS6GS/dADj9MoS6fzdAdoJu4QvyGSB+miihQ0NztBkKYBZSDAhDn +5T6JrDadrXumFLL8eW8b/n1JtEYOy2asVEtFiET42e6N2R2kQFg1tvmP1er4cmdN +d/X0L0cfMNFzihXv5LCzUI4h7Ot1ZU83K9XhITN2fqaHvYaJKsl69dcO4NujgRs9 +EALOE4hH1fX4pMXhv0vE1wy8EaQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEKDCCAxCgAwIBAgICDKUwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgUm9vdCBD +QTAeFw0xMDEyMDMxODUyMDBaFw0zODA0MjAxODUyMDBaMG8xCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxGTAXBgNVBAMTEENBUyBUZXN0IFJvb3Qg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCe6snxpRVgR8OgTwve +Lb326AeEpplLEDeyR9IuawwfIovCFcWhyuH5CJq3KxlwEKbYXr/Dd1xRcigZDMJX +dqJ/Ke4Gsq2fNyur1pk0LUOTGJC7sVeSdbfXEVR3hDns27q/avinvYb1rwfr6Q5e +mJWrqE4J30Ff0AZ1gGXUF8hqYsxocMRQ1hezdSH4B+ukJY48FOxfEoxcTsqSNSHg +yhbVuZ7pJXh/JcCCloNPLvEcDsxwKcXNGdMrO6PIF7DuIFIMMERyhzDY/ZuKBZJv +bhY0Lz2RyMZh2rA6kw7YQLpTzxqakz+1WSixKY++fmeG6VXK/9y4xi4YpfQ6rq2l +Y5vvAgMBAAGjgc0wgcowHQYDVR0OBBYEFPEaLpPHQoEbGysbG2Uo3a+N6kPyMIGa +BgNVHSMEgZIwgY+AFPEaLpPHQoEbGysbG2Uo3a+N6kPyoXOkcTBvMQswCQYDVQQG +EwJVUzERMA8GA1UECBMIQ29sb3JhZG8xFDASBgNVBAcTC1dlc3RtaW5zdGVyMQ4w +DAYDVQQKEwVKYXNpZzEMMAoGA1UECxMDQ0FTMRkwFwYDVQQDExBDQVMgVGVzdCBS +b290IENBggIMpTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAmDP2G +r/Vgowgt0ncwkDc5hiyd22cavavZ2n8+3mBaun2vc7ytbdhOMWSVrO5RzWj+IdGx +2MuTmtS3uoAMxyKLYlSD1DyOZpBeFj0+YCMeNr42bE7zhQyogMockbMAxgC0oAtL +mL1G+zVEnva76HLzkes2faP65u36LYwiZc5Cld3hHI8R1F18Vj9us+XAHMZlK7fS +c6d7FpR8SOUP0LyRYDaD26DuU3TjoufIpQFuMpzY5Z5LK5zseVnR8n/Ue4UzBJCh +1AAhMOyhfBB8M5Ypkn+csHcdd/Jd/hMy0N8BniGHRFug0RMlgY34YNK1sLo59qUV +y9ThiKbmq1N+znoL +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/src/test/resources/user-valid-distcrl.crt b/cas-server-support-x509/src/test/resources/user-valid-distcrl.crt new file mode 100644 index 000000000000..ad907c10b538 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/user-valid-distcrl.crt @@ -0,0 +1,88 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3242 (0xcaa) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Validity + Not Before: Jan 26 16:00:57 2011 GMT + Not After : Dec 21 16:00:57 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=Anthony + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:7b:c9:05:71:72:77:e4:7a:9b:3f:5f:5b:5d: + 2c:84:65:8a:3e:45:d5:29:71:cd:a0:01:46:f0:76: + a3:17:52:14:70:1c:68:11:b5:1e:de:4f:2a:c5:72: + d9:c5:dd:f3:e2:f3:5b:4b:c0:f1:92:29:3b:8a:1f: + 0a:56:39:fe:02:28:cc:32:76:81:d8:db:3e:21:62: + dd:89:24:07:0b:d9:3a:ae:19:78:61:a0:b3:d5:ed: + e0:00:02:87:cb:ff:79:e6:e9:b0:b7:38:a6:db:78: + e1:cc:60:dd:6b:d7:4a:97:80:81:76:3c:6d:91:e4: + de:09:cf:a0:e6:75:29:ba:c6:d1:9c:7d:90:5d:da: + af:e4:a0:9e:fa:7b:2a:9e:da:e4:d4:64:1e:4c:25: + 77:f0:3c:f5:ea:7a:8c:e8:9f:ed:ab:66:ee:c3:c7: + 72:8c:25:30:82:7e:3f:28:22:a1:b5:6a:1d:64:8f: + 8f:45:62:33:2e:f6:bb:0b:88:93:b7:0a:45:55:d6: + 2d:84:72:62:5d:26:ec:8b:db:58:4f:b8:23:48:ed: + be:f4:55:51:8c:5b:25:2e:cf:16:12:30:d4:05:68: + e4:78:db:ab:bb:66:89:19:70:d0:a9:09:48:44:fb: + f4:7a:6c:12:55:fd:14:13:da:1f:14:c4:de:83:12: + c6:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 27:3E:80:D5:75:E0:F6:D1:53:E0:68:92:6F:67:05:DB:36:08:95:21 + X509v3 Authority Key Identifier: + keyid:19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + + X509v3 CRL Distribution Points: + URI:http://localhost:8085/ca.crl + URI:http://example.com/ca.crl + + Signature Algorithm: sha1WithRSAEncryption + 1f:e3:c6:d6:87:cb:3f:ff:5f:c4:6f:b8:eb:69:8e:15:0c:d4: + 95:73:17:6a:5e:5d:39:ab:67:12:c8:ec:f5:bb:49:40:e4:85: + 8e:83:db:b8:2c:0a:c8:f0:74:1d:23:32:0a:96:de:15:e9:ff: + 1b:87:aa:7b:ed:b9:d0:d1:32:d2:75:d5:83:be:87:b5:f9:7c: + 41:23:e1:8d:01:4b:aa:4a:49:a6:d6:c7:45:13:f5:23:a5:44: + 5a:75:2f:ac:2b:11:c7:bc:a8:50:b8:9f:90:93:c6:d0:e8:df: + 6a:b8:4f:ec:4e:fd:41:b2:26:97:4a:d4:a5:5f:42:c0:ad:15: + 20:b0:92:8e:bb:a0:41:f2:92:f3:f3:83:f2:4a:97:35:c1:21: + 6f:1d:32:0f:e7:23:96:06:fc:10:4a:4b:8d:dc:48:00:30:68: + 0f:de:67:97:1d:6b:52:6e:1f:22:8b:f9:cd:55:75:33:a1:55: + 25:b8:bc:f9:90:15:80:25:79:2c:3e:3d:db:64:ae:7c:b7:5a: + da:66:4d:67:2f:15:9b:95:a6:67:cd:77:49:7f:95:b0:b7:72: + 93:a0:f9:f8:bf:70:d6:87:d5:02:26:bf:ac:3b:03:88:a9:83: + 76:72:27:f6:84:58:3f:5b:cd:db:09:aa:6b:32:49:d7:bb:d8: + 9c:8e:20:13 +-----BEGIN CERTIFICATE----- +MIIEHjCCAwagAwIBAgICDKowDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMjYxNjAwNTdaFw0zMjEyMjExNjAwNTdaMGYxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxEDAOBgNVBAMTB0FudGhvbnkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLe8kFcXJ35HqbP19bXSyEZYo+RdUp +cc2gAUbwdqMXUhRwHGgRtR7eTyrFctnF3fPi81tLwPGSKTuKHwpWOf4CKMwydoHY +2z4hYt2JJAcL2TquGXhhoLPV7eAAAofL/3nm6bC3OKbbeOHMYN1r10qXgIF2PG2R +5N4Jz6DmdSm6xtGcfZBd2q/koJ76eyqe2uTUZB5MJXfwPPXqeozon+2rZu7Dx3KM +JTCCfj8oIqG1ah1kj49FYjMu9rsLiJO3CkVV1i2EcmJdJuyL21hPuCNI7b70VVGM +WyUuzxYSMNQFaOR426u7ZokZcNCpCUhE+/R6bBJV/RQT2h8UxN6DEsYBAgMBAAGj +gcwwgckwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0 +ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFCc+gNV14PbRU+Bokm9nBds2CJUhMB8G +A1UdIwQYMBaAFBkbeI2TYrJJZpBVblCz0K9h6znzME4GA1UdHwRHMEUwIqAgoB6G +HGh0dHA6Ly9sb2NhbGhvc3Q6ODA4NS9jYS5jcmwwH6AdoBuGGWh0dHA6Ly9leGFt +cGxlLmNvbS9jYS5jcmwwDQYJKoZIhvcNAQEFBQADggEBAB/jxtaHyz//X8RvuOtp +jhUM1JVzF2peXTmrZxLI7PW7SUDkhY6D27gsCsjwdB0jMgqW3hXp/xuHqnvtudDR +MtJ11YO+h7X5fEEj4Y0BS6pKSabWx0UT9SOlRFp1L6wrEce8qFC4n5CTxtDo32q4 +T+xO/UGyJpdK1KVfQsCtFSCwko67oEHykvPzg/JKlzXBIW8dMg/nI5YG/BBKS43c +SAAwaA/eZ5cda1JuHyKL+c1VdTOhVSW4vPmQFYAleSw+Pdtkrny3WtpmTWcvFZuV +pmfNd0l/lbC3cpOg+fi/cNaH1QImv6w7A4ipg3ZyJ/aEWD9bzdsJqmsySde72JyO +IBM= +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/src/test/resources/user-valid-keyUsage.crt b/cas-server-support-x509/src/test/resources/user-valid-keyUsage.crt new file mode 100644 index 000000000000..ee510cdf507e --- /dev/null +++ b/cas-server-support-x509/src/test/resources/user-valid-keyUsage.crt @@ -0,0 +1,84 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3240 (0xca8) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Validity + Not Before: Jan 24 18:56:45 2011 GMT + Not After : Dec 19 18:56:45 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=Albert + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:7b:c9:05:71:72:77:e4:7a:9b:3f:5f:5b:5d: + 2c:84:65:8a:3e:45:d5:29:71:cd:a0:01:46:f0:76: + a3:17:52:14:70:1c:68:11:b5:1e:de:4f:2a:c5:72: + d9:c5:dd:f3:e2:f3:5b:4b:c0:f1:92:29:3b:8a:1f: + 0a:56:39:fe:02:28:cc:32:76:81:d8:db:3e:21:62: + dd:89:24:07:0b:d9:3a:ae:19:78:61:a0:b3:d5:ed: + e0:00:02:87:cb:ff:79:e6:e9:b0:b7:38:a6:db:78: + e1:cc:60:dd:6b:d7:4a:97:80:81:76:3c:6d:91:e4: + de:09:cf:a0:e6:75:29:ba:c6:d1:9c:7d:90:5d:da: + af:e4:a0:9e:fa:7b:2a:9e:da:e4:d4:64:1e:4c:25: + 77:f0:3c:f5:ea:7a:8c:e8:9f:ed:ab:66:ee:c3:c7: + 72:8c:25:30:82:7e:3f:28:22:a1:b5:6a:1d:64:8f: + 8f:45:62:33:2e:f6:bb:0b:88:93:b7:0a:45:55:d6: + 2d:84:72:62:5d:26:ec:8b:db:58:4f:b8:23:48:ed: + be:f4:55:51:8c:5b:25:2e:cf:16:12:30:d4:05:68: + e4:78:db:ab:bb:66:89:19:70:d0:a9:09:48:44:fb: + f4:7a:6c:12:55:fd:14:13:da:1f:14:c4:de:83:12: + c6:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 27:3E:80:D5:75:E0:F6:D1:53:E0:68:92:6F:67:05:DB:36:08:95:21 + X509v3 Authority Key Identifier: + keyid:19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + + Signature Algorithm: sha1WithRSAEncryption + a0:9a:05:61:8d:6d:9c:03:23:11:59:dc:98:62:eb:49:2a:ea: + 60:d8:31:f3:c2:5a:fb:49:36:3d:b7:0b:2a:16:91:7b:31:ad: + 25:10:b9:f1:cb:d0:89:c2:90:7d:59:9a:12:e1:f6:97:e9:93: + 5b:14:a6:2a:bd:70:f2:38:19:fc:22:44:76:5a:f9:45:1a:2d: + 5a:84:fe:ab:ca:76:00:94:1d:eb:c6:fb:52:bd:ef:97:97:53: + a3:0f:90:ad:1e:d8:30:ed:f4:90:22:3c:eb:08:95:ac:fe:0a: + 98:72:12:6a:84:60:85:86:44:d4:bc:22:ed:92:9f:05:84:5d: + 34:9a:01:84:ea:a0:06:86:5f:91:50:28:fb:8f:3f:8a:6e:a8: + 35:e9:32:40:f1:04:61:88:25:91:ca:59:72:63:75:17:a7:93: + 56:98:a1:7e:51:3f:52:47:91:ff:be:57:fb:ff:2a:7a:ee:1d: + e1:25:ae:97:d1:13:fd:41:03:3c:91:91:44:df:b3:35:14:83: + ab:36:c6:c7:15:f1:32:de:ba:ba:6e:a5:78:65:f5:4a:8d:dc: + c3:84:1a:c2:a9:cd:8a:49:2f:5f:f0:b1:0f:09:09:bc:1c:bd: + 22:87:37:05:3d:71:85:37:55:b4:eb:05:7e:cb:60:fc:ce:5b: + 23:3e:0d:55 +-----BEGIN CERTIFICATE----- +MIID2jCCAsKgAwIBAgICDKgwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMjQxODU2NDVaFw0zMjEyMTkxODU2NDVaMGUxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxDzANBgNVBAMTBkFsYmVydDCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMt7yQVxcnfkeps/X1tdLIRlij5F1Slx +zaABRvB2oxdSFHAcaBG1Ht5PKsVy2cXd8+LzW0vA8ZIpO4ofClY5/gIozDJ2gdjb +PiFi3YkkBwvZOq4ZeGGgs9Xt4AACh8v/eebpsLc4ptt44cxg3WvXSpeAgXY8bZHk +3gnPoOZ1KbrG0Zx9kF3ar+Sgnvp7Kp7a5NRkHkwld/A89ep6jOif7atm7sPHcowl +MIJ+PygiobVqHWSPj0ViMy72uwuIk7cKRVXWLYRyYl0m7IvbWE+4I0jtvvRVUYxb +JS7PFhIw1AVo5Hjbq7tmiRlw0KkJSET79HpsElX9FBPaHxTE3oMSxgECAwEAAaOB +iTCBhjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAsBglghkgBhvhCAQ0EHxYdT3Bl +blNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFCc+gNV14PbRU+Bo +km9nBds2CJUhMB8GA1UdIwQYMBaAFBkbeI2TYrJJZpBVblCz0K9h6znzMA0GCSqG +SIb3DQEBBQUAA4IBAQCgmgVhjW2cAyMRWdyYYutJKupg2DHzwlr7STY9twsqFpF7 +Ma0lELnxy9CJwpB9WZoS4faX6ZNbFKYqvXDyOBn8IkR2WvlFGi1ahP6rynYAlB3r +xvtSve+Xl1OjD5CtHtgw7fSQIjzrCJWs/gqYchJqhGCFhkTUvCLtkp8FhF00mgGE +6qAGhl+RUCj7jz+Kbqg16TJA8QRhiCWRyllyY3UXp5NWmKF+UT9SR5H/vlf7/yp6 +7h3hJa6X0RP9QQM8kZFE37M1FIOrNsbHFfEy3rq6bqV4ZfVKjdzDhBrCqc2KSS9f +8LEPCQm8HL0ihzcFPXGFN1W06wV+y2D8zlsjPg1V +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/src/test/resources/user-valid.crt b/cas-server-support-x509/src/test/resources/user-valid.crt new file mode 100644 index 000000000000..9815bba869c9 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/user-valid.crt @@ -0,0 +1,82 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3237 (0xca5) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Validity + Not Before: Jan 19 18:27:57 2011 GMT + Not After : Dec 14 18:27:57 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=Alice + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:7b:c9:05:71:72:77:e4:7a:9b:3f:5f:5b:5d: + 2c:84:65:8a:3e:45:d5:29:71:cd:a0:01:46:f0:76: + a3:17:52:14:70:1c:68:11:b5:1e:de:4f:2a:c5:72: + d9:c5:dd:f3:e2:f3:5b:4b:c0:f1:92:29:3b:8a:1f: + 0a:56:39:fe:02:28:cc:32:76:81:d8:db:3e:21:62: + dd:89:24:07:0b:d9:3a:ae:19:78:61:a0:b3:d5:ed: + e0:00:02:87:cb:ff:79:e6:e9:b0:b7:38:a6:db:78: + e1:cc:60:dd:6b:d7:4a:97:80:81:76:3c:6d:91:e4: + de:09:cf:a0:e6:75:29:ba:c6:d1:9c:7d:90:5d:da: + af:e4:a0:9e:fa:7b:2a:9e:da:e4:d4:64:1e:4c:25: + 77:f0:3c:f5:ea:7a:8c:e8:9f:ed:ab:66:ee:c3:c7: + 72:8c:25:30:82:7e:3f:28:22:a1:b5:6a:1d:64:8f: + 8f:45:62:33:2e:f6:bb:0b:88:93:b7:0a:45:55:d6: + 2d:84:72:62:5d:26:ec:8b:db:58:4f:b8:23:48:ed: + be:f4:55:51:8c:5b:25:2e:cf:16:12:30:d4:05:68: + e4:78:db:ab:bb:66:89:19:70:d0:a9:09:48:44:fb: + f4:7a:6c:12:55:fd:14:13:da:1f:14:c4:de:83:12: + c6:01 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 27:3E:80:D5:75:E0:F6:D1:53:E0:68:92:6F:67:05:DB:36:08:95:21 + X509v3 Authority Key Identifier: + keyid:19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + + Signature Algorithm: sha1WithRSAEncryption + 6a:dd:33:d9:8f:d1:8c:da:66:6c:86:eb:81:2f:b8:f7:37:8b: + ae:3c:af:a2:68:88:f7:6e:1f:53:90:6d:89:ff:6a:3e:5e:82: + 5b:79:e9:57:94:82:39:32:e2:ab:a5:ad:90:5b:cd:3a:b4:b5: + f3:96:d1:ee:ac:03:d0:69:60:42:41:a5:c7:6e:e0:68:9d:c5: + 39:4b:53:26:27:72:cc:bb:72:76:a8:c2:be:79:04:35:88:2f: + 14:f8:06:f9:f2:3d:dd:9a:8e:5a:8d:9e:8c:55:a6:02:97:73: + 2a:09:2d:4b:6a:a4:2b:19:5f:28:78:0f:40:fd:4d:8c:19:54: + 1a:1c:04:f4:15:f6:e1:8a:59:9f:99:80:a0:00:7a:56:b5:77: + 8b:c8:5f:67:ed:28:8a:3d:3e:69:08:88:71:ac:37:a4:a2:85: + 1c:6e:a3:b4:bf:ad:7e:43:f9:ca:f8:50:37:10:4a:42:53:5c: + 35:04:68:91:5e:fe:c4:b4:66:d2:5d:bd:50:58:ba:5d:42:04: + 99:2a:1b:a2:be:ff:ac:49:f4:0e:2d:e1:a3:20:21:d5:bf:f5: + bb:f1:e1:e0:52:3e:d4:fe:f3:0d:f4:ca:2e:86:ae:15:95:b7: + 69:cc:9a:c4:40:4e:85:c7:fc:83:32:09:f5:ea:68:96:aa:df: + 36:96:2d:3a +-----BEGIN CERTIFICATE----- +MIIDyjCCArKgAwIBAgICDKUwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBD +QTAeFw0xMTAxMTkxODI3NTdaFw0zMjEyMTQxODI3NTdaMGQxCzAJBgNVBAYTAlVT +MREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1pbnN0ZXIxDjAMBgNV +BAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxDjAMBgNVBAMTBUFsaWNlMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy3vJBXFyd+R6mz9fW10shGWKPkXVKXHN +oAFG8HajF1IUcBxoEbUe3k8qxXLZxd3z4vNbS8Dxkik7ih8KVjn+AijMMnaB2Ns+ +IWLdiSQHC9k6rhl4YaCz1e3gAAKHy/955umwtzim23jhzGDda9dKl4CBdjxtkeTe +Cc+g5nUpusbRnH2QXdqv5KCe+nsqntrk1GQeTCV38Dz16nqM6J/tq2buw8dyjCUw +gn4/KCKhtWodZI+PRWIzLva7C4iTtwpFVdYthHJiXSbsi9tYT7gjSO2+9FVRjFsl +Ls8WEjDUBWjkeNuru2aJGXDQqQlIRPv0emwSVf0UE9ofFMTegxLGAQIDAQABo3sw +eTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBD +ZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUJz6A1XXg9tFT4GiSb2cF2zYIlSEwHwYDVR0j +BBgwFoAUGRt4jZNisklmkFVuULPQr2HrOfMwDQYJKoZIhvcNAQEFBQADggEBAGrd +M9mP0YzaZmyG64EvuPc3i648r6JoiPduH1OQbYn/aj5eglt56VeUgjky4qulrZBb +zTq0tfOW0e6sA9BpYEJBpcdu4GidxTlLUyYncsy7cnaowr55BDWILxT4BvnyPd2a +jlqNnoxVpgKXcyoJLUtqpCsZXyh4D0D9TYwZVBocBPQV9uGKWZ+ZgKAAela1d4vI +X2ftKIo9PmkIiHGsN6SihRxuo7S/rX5D+cr4UDcQSkJTXDUEaJFe/sS0ZtJdvVBY +ul1CBJkqG6K+/6xJ9A4t4aMgIdW/9bvx4eBSPtT+8w30yi6GrhWVt2nMmsRAToXH +/IMyCfXqaJaq3zaWLTo= +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/src/test/resources/userCA-expired.crl b/cas-server-support-x509/src/test/resources/userCA-expired.crl new file mode 100644 index 000000000000..12bdbd41d742 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/userCA-expired.crl @@ -0,0 +1,15 @@ +-----BEGIN X509 CRL----- +MIICTzCCATcCAQEwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMxETAPBgNV +BAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UEChMFSmFz +aWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBDQRcNMTEw +MTI2MTYyMjAzWhcNMTEwMTI2MTcyMjAzWjCBgzA7AgIMpxcNMTEwMTE5MTg0MTM2 +WjAmMAoGA1UdFQQDCgEBMBgGA1UdGAQRGA8yMDEwMDExOTE5MDAwMFowIQICDKsX +DTExMDEyNjE2MTA1NlowDDAKBgNVHRUEAwoBATAhAgIMrBcNMTEwMTI2MTYyMTUz +WjAMMAoGA1UdFQQDCgEBoA4wDDAKBgNVHRQEAwIBCDANBgkqhkiG9w0BAQUFAAOC +AQEAGwSEXs1fLW5Ba1NzCx7eststFRFGOpEkssejPexg2VqZzF9lC6wuV3+cHhIh +UhiKCP2yHzqlDkDagKscyG+xq31qfCGnMRjlAAUZz2PWpdTxtuJJYTHJABBb6Uyx +i47ou8JhWj1KMjM7I0HGML5sjFVhoEhL7kwPq6oyXZeoNFbMQKzIYeDLCu6qkRmk +oJdFaM+iMmU0rTLg4XtteAxpXpjsXsz2QTohclc5anRy6S5grT13GdVPx4nKbaNP +sk9CafcGm8z/Dt/tPqUMLVTiW7CyfAqVlUQgmmht1ecfuxkAkzsXW92Q0RjFxIK8 +12dnFuEv7ncz+Z5ujJ4XcxKgbw== +-----END X509 CRL----- diff --git a/cas-server-support-x509/src/test/resources/userCA-valid.crl b/cas-server-support-x509/src/test/resources/userCA-valid.crl new file mode 100644 index 000000000000..50615d7e7031 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/userCA-valid.crl @@ -0,0 +1,16 @@ +-----BEGIN X509 CRL----- +MIICcjCCAVoCAQEwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMxETAPBgNV +BAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UEChMFSmFz +aWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgVXNlciBDQRcNMTEw +MTMxMTcwNDUyWhcNMzIxMjI2MTcwNDUyWjCBpjA7AgIMpxcNMTEwMTE5MTg0MTM2 +WjAmMAoGA1UdFQQDCgEBMBgGA1UdGAQRGA8yMDEwMDExOTE5MDAwMFowIQICDKsX +DTExMDEyNjE2MTA1NlowDDAKBgNVHRUEAwoBATAhAgIMrBcNMTEwMTI2MTYyMTUz +WjAMMAoGA1UdFQQDCgEBMCECAgytFw0xMTAxMzExNzA0MTJaMAwwCgYDVR0VBAMK +AQGgDjAMMAoGA1UdFAQDAgEKMA0GCSqGSIb3DQEBBQUAA4IBAQB/7omhF61Z3FQT +usNs6lRrwvR3QTOKcBxl5fkTjYcuRUgDmqeOi9NXTq6Nynx0lRxzA0I1X7d380px +Q8F/Pyl/UrgcJ0wK+oR2q2a+rzGxkAHEpfQmFMsOyQCsp6eRGNhmeSa7APj/rqll +DlR13bPW0w5SxLZls2yOoYkXAdNZmq9MYgAq7rQNWAmr4dKr1kejb6kK3B3FDuxt +XrfPyDf+H49Ynb/3jhmsZ6dUst3S9wFLAinskChFmkNaelNsyTD6Gx6xCsL5FKgh +9dqPOgxjJFPrcFnqJ1bDsQ4m0totiHpZU4+Aid4+majeh3o4o3vy5hcrul7OY9NI +MhpDmU6F +-----END X509 CRL----- diff --git a/cas-server-support-x509/src/test/resources/userCA.crt b/cas-server-support-x509/src/test/resources/userCA.crt new file mode 100644 index 000000000000..f431a7723e68 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/userCA.crt @@ -0,0 +1,84 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3237 (0xca5) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test Intermediate CA + Validity + Not Before: Jan 19 18:25:39 2011 GMT + Not After : Dec 14 18:25:39 2032 GMT + Subject: C=US, ST=Colorado, L=Westminster, O=Jasig, OU=CAS, CN=CAS Test User CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:ab:9d:39:88:09:d6:e8:41:65:21:b8:8c:a2:b7: + d6:6c:a9:cf:f5:fe:f9:c7:ec:e3:b1:b2:16:07:8d: + 2a:8d:d8:bc:7b:1c:73:85:09:82:c9:a2:9a:af:0a: + b3:4e:dc:53:bd:9f:27:d1:6d:91:e4:77:8d:fe:bc: + 74:36:26:54:08:81:e6:1f:74:d6:5a:a4:41:7d:95: + 93:5c:5b:17:1e:a3:3b:90:08:96:2d:6a:e8:00:c1: + b1:3c:0a:0b:93:2d:c4:00:27:d5:ad:ed:d1:b2:66: + 44:2c:60:b0:2f:92:26:7b:d4:8b:24:01:d6:62:e2: + 48:17:2e:3c:0d:15:fc:df:42:59:90:30:84:0b:81: + 86:2a:01:8b:58:ba:15:03:ad:6e:07:58:cf:a1:f7: + 2e:6e:c8:5c:f2:b1:ae:90:36:67:d4:39:96:b8:04: + b4:66:b0:f7:46:2a:ba:21:43:46:b9:fb:93:d1:39: + d6:fd:40:b4:d2:9d:e9:d8:4f:dc:ae:14:8e:73:05: + 26:8a:5c:21:5c:39:a5:c0:9a:66:1c:b8:9e:94:51: + ee:71:8c:99:a9:c2:2e:39:00:85:04:d1:14:a7:23: + 00:4f:7b:52:35:61:0a:d2:0a:64:c9:32:13:7d:46: + 7d:35:f6:b1:31:48:38:60:6a:9b:b5:bf:3d:23:35: + 91:21 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 19:1B:78:8D:93:62:B2:49:66:90:55:6E:50:B3:D0:AF:61:EB:39:F3 + X509v3 Authority Key Identifier: + keyid:12:38:AD:05:C8:59:B1:10:9F:FF:A8:6B:2E:CE:03:52:FF:59:D8:7F + DirName:/C=US/ST=Colorado/L=Westminster/O=Jasig/OU=CAS/CN=CAS Test Root CA + serial:0C:A5 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 05:95:6a:d7:a8:b5:cc:94:a2:54:1a:27:04:44:55:f2:cd:2f: + d3:15:ee:5c:80:99:63:14:b4:23:86:fe:6c:90:de:3a:ac:26: + 68:9c:ef:ab:55:56:00:77:4e:dc:93:32:32:eb:78:da:2a:fb: + 86:2f:7a:d0:e3:d8:b3:ec:02:57:60:41:fb:4d:b2:58:45:26: + 9a:ba:35:96:6b:ff:0c:6e:68:d4:ca:69:74:cc:55:e5:ef:74: + 07:bf:8a:c0:7a:63:22:fd:06:1d:93:18:0d:c2:8f:12:45:3a: + 99:0f:7f:20:23:27:3a:8b:75:2c:26:59:37:d6:75:76:4b:4b: + 87:34:a8:99:f9:eb:90:bf:5a:e5:43:a0:fa:d6:42:cf:1e:c4: + 58:43:c2:44:3a:90:84:5c:82:83:db:6b:c4:21:e7:5e:39:ea: + ce:4f:be:28:e1:bf:48:e3:d6:fb:0a:88:92:bd:b3:df:2c:0c: + 66:d2:23:5d:03:89:5f:de:4f:ae:8f:05:47:9a:aa:84:70:b5: + 74:bd:19:2c:cd:ca:0b:f5:9a:82:5f:1b:e2:a7:c9:0f:9a:bc: + c2:69:f4:5c:f2:29:cb:de:c2:f6:63:8d:df:15:b6:34:64:0d: + aa:a3:42:32:e6:5b:eb:57:10:e3:01:b1:78:b6:fc:a2:ff:62: + 3f:ba:43:4a +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgICDKUwDQYJKoZIhvcNAQEFBQAwdzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UE +ChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEhMB8GA1UEAxMYQ0FTIFRlc3QgSW50ZXJt +ZWRpYXRlIENBMB4XDTExMDExOTE4MjUzOVoXDTMyMTIxNDE4MjUzOVowbzELMAkG +A1UEBhMCVVMxETAPBgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3Rl +cjEOMAwGA1UEChMFSmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRl +c3QgVXNlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKudOYgJ +1uhBZSG4jKK31mypz/X++cfs47GyFgeNKo3YvHscc4UJgsmimq8Ks07cU72fJ9Ft +keR3jf68dDYmVAiB5h901lqkQX2Vk1xbFx6jO5AIli1q6ADBsTwKC5MtxAAn1a3t +0bJmRCxgsC+SJnvUiyQB1mLiSBcuPA0V/N9CWZAwhAuBhioBi1i6FQOtbgdYz6H3 +Lm7IXPKxrpA2Z9Q5lrgEtGaw90YquiFDRrn7k9E51v1AtNKd6dhP3K4UjnMFJopc +IVw5pcCaZhy4npRR7nGMmanCLjkAhQTRFKcjAE97UjVhCtIKZMkyE31GfTX2sTFI +OGBqm7W/PSM1kSECAwEAAaOBzTCByjAdBgNVHQ4EFgQUGRt4jZNisklmkFVuULPQ +r2HrOfMwgZoGA1UdIwSBkjCBj4AUEjitBchZsRCf/6hrLs4DUv9Z2H+hc6RxMG8x +CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhDb2xvcmFkbzEUMBIGA1UEBxMLV2VzdG1p +bnN0ZXIxDjAMBgNVBAoTBUphc2lnMQwwCgYDVQQLEwNDQVMxGTAXBgNVBAMTEENB +UyBUZXN0IFJvb3QgQ0GCAgylMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBAAWVateotcyUolQaJwREVfLNL9MV7lyAmWMUtCOG/myQ3jqsJmic76tVVgB3 +TtyTMjLreNoq+4YvetDj2LPsAldgQftNslhFJpq6NZZr/wxuaNTKaXTMVeXvdAe/ +isB6YyL9Bh2TGA3CjxJFOpkPfyAjJzqLdSwmWTfWdXZLS4c0qJn565C/WuVDoPrW +Qs8exFhDwkQ6kIRcgoPba8Qh51456s5Pvijhv0jj1vsKiJK9s98sDGbSI10DiV/e +T66PBUeaqoRwtXS9GSzNygv1moJfG+KnyQ+avMJp9FzyKcvewvZjjd8VtjRkDaqj +QjLmW+tXEOMBsXi2/KL/Yj+6Q0o= +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/src/test/resources/uservalid-encoded-crl.crt b/cas-server-support-x509/src/test/resources/uservalid-encoded-crl.crt new file mode 100644 index 000000000000..235d30ea8ccf Binary files /dev/null and b/cas-server-support-x509/src/test/resources/uservalid-encoded-crl.crt differ diff --git a/cas-server-support-x509/src/test/resources/x509-ctop-resolver-gazzo.crt b/cas-server-support-x509/src/test/resources/x509-ctop-resolver-gazzo.crt new file mode 100644 index 000000000000..5e2bd5e001ee --- /dev/null +++ b/cas-server-support-x509/src/test/resources/x509-ctop-resolver-gazzo.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDqjCCApICBBAzxNcwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMxETAP +BgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UEChMF +SmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgUm9vdCBDQTAe +Fw0xMDEyMDMyMDQ0MjlaFw0yMDExMzAyMDQ0MjlaMIHDMQswCQYDVQQGEwJVUzER +MA8GA1UECBMIVmlyZ2luaWExEzARBgNVBAcTCkJsYWNrc2J1cmcxPDA6BgNVBAoT +M1ZpcmdpbmlhIFBvbHl0ZWNobmljIEluc3RpdHV0ZSBhbmQgU3RhdGUgVW5pdmVy +c2l0eTETMBEGA1UECxMKTWlkZGxld2FyZTElMCMGA1UEAxMcR2F6emFsb2RkaSBQ +LiBXaXNod2FzaGluZ3RvbjESMBAGA1UEBRMJMjcxODI4MTgzMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu5CcShUOQhKg5Ze+L7SshZxKhay4uKwB5Q++ +MtiJbWzNJipOT9RMq0945A6I2fIEXy6bS/r6WsSUnCJSdT7bt+oo8Pj+uLKSQCjl +v4CM5rFDY1hg1K5IUFiG4HW9ey2YHZqD/NkVU41L32S4Y4mJvzpPqWRQ/hPCLMaz +fQgtcHbENbw2bfU7uLePjaCIEQ5J4LiyOcZsWLLWPLFI7B2WRYjX/m6yJmXQQCat +xexGwy9Ev9GbdHZZZH8IFShrHpUaK97LoD+XDFlC3wuokKacWhutEyvizwCx6VVS +7gCtDEQkPXAhGQ98tMwWWvJnnPL+IK3kS4GY7jS1n+1rdvLo7QIDAQABMA0GCSqG +SIb3DQEBBQUAA4IBAQANY6rKzbrWuTZUIJ51FfHrVA7NxQsYIehJPcxVhUksRdem +04FdboqG1yXDPjSGfcsrTY1sxQcIlHBDtbb4Ti7GujLRKupkmCepFVM+rhLTkl4h +HgFxYFVR9CypM6gw5ZZB/jqdb2Bfpu1Zfys2VX51aXKZeD3kMZ97YkHDi/5962fp +JB9zLw5ljG5ud7kNouLdFx6bAD4Jx3Esu8UuzDs6liYfU6RuyubnjL3znRoCyy+f +Bm0lnbF79rsLH+sP1yuz066yxW5UgiIAxJ8Z74zZIuM/fsGe14qF+9Z8/+hyGRwY +KC/W9+73S+NRIu5wb9k7PEVoR51Ho19Pm0RhAQx6 +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/src/test/resources/x509-ctop-resolver-hizzy.crt b/cas-server-support-x509/src/test/resources/x509-ctop-resolver-hizzy.crt new file mode 100644 index 000000000000..de154f2b7973 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/x509-ctop-resolver-hizzy.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDuTCCAqECBBK5sKEwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMxETAP +BgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UEChMF +SmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgUm9vdCBDQTAe +Fw0xMDEyMDMxODU5MDRaFw0yMDExMzAxODU5MDRaMIHSMQswCQYDVQQGEwJVUzER +MA8GA1UECBMIVmlyZ2luaWExEzARBgNVBAcTCkJsYWNrc2J1cmcxPDA6BgNVBAoT +M1ZpcmdpbmlhIFBvbHl0ZWNobmljIEluc3RpdHV0ZSBhbmQgU3RhdGUgVW5pdmVy +c2l0eTETMBEGA1UECxMKTWlkZGxld2FyZTErMCkGA1UEAxMiSGl6em9nYXJ0aGlu +Z3RvbiBJLlMuIFBsZWFraW5zZW5zZTEbMBkGCSqGSIb3DQEJARYMaGl6enlAdnQu +ZWR1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv6Bm7SyM2fPV0YPI +468NpXDbacYtljSBdL7sUccEyd8tEB9SHU+q8NPPQaagSbtNzGAYvpq6Wgr8HS5R +XtJ4pZ1WqHE5Nw2HSIBw1Ub/53eJ4z9mswU5NmKni8q0KuF8+fG6quV6qzxqVU/H +Vi90jNuxJ1dfGCNh5zHGaootuVyHstuLVi1tvAaCp7AlIID1J4z7K9IQfphxz4ih +p/BU6m1C8Hsyr4o0zb5l2nQ01lLwFdA1FClTLQG+0/RLYZkwOaBHiRd6rN30VKgc +5EoQvQvGiC4dMM+Gr713RZKBqfCd0S0xBraGTa54C5EqlqrNy/9uVUCcxkd1ARIp +1PnKNwIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQCJ9Sua+RTEAe1+cCAFaPwKp1fq +HTqRAW9mJsVwVbxpK24OhXD+/meX57asjEWvJqf6FSdLZzJOXdpQclwDF5N5wdQC +/6lyOCXsi3guKLPayMs7ig0n0JEzF+V+oYFMAauqhKeH8rrndKnDBSS+lslH2rZl +ZCN8y+OyPLIr2qyAjq+ky/n2VFJ5Ukd7LlNdHvMwLY6ECQJdDzwxJMDP5EnPKj7S +B0GgrC+fu6q3KmjbBZSGg7TWQo1El7xcGA46EYs23+APo6zYGifCsRf08qvecSdG +Kp2oxD5Aq37zsfeAIdZxZblVVKV2uUHSvWb8Cqx5gTIyChHI34H8kNNjvpg6 +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/src/test/resources/x509-ctop-resolver-jacky.crt b/cas-server-support-x509/src/test/resources/x509-ctop-resolver-jacky.crt new file mode 100644 index 000000000000..540e41583543 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/x509-ctop-resolver-jacky.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDRzCCAi8CBAht6ywwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMxETAP +BgNVBAgTCENvbG9yYWRvMRQwEgYDVQQHEwtXZXN0bWluc3RlcjEOMAwGA1UEChMF +SmFzaWcxDDAKBgNVBAsTA0NBUzEZMBcGA1UEAxMQQ0FTIFRlc3QgUm9vdCBDQTAe +Fw0xMDEyMDYxOTUwNDRaFw0yMDEyMDMxOTUwNDRaMGExEzARBgoJkiaJk/IsZAEZ +FgNlZHUxEjAQBgoJkiaJk/IsZAEZFgJ2dDEVMBMGCgmSJomT8ixkAQETBWphY2t5 +MR8wHQYDVQQDExZKYXNjYXJuZWxsYSBFbGxhZ3dvbnRvMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAuGjeYarpOLpHT8M7JKB2HNxMuWI8yuKCgGJXDqsK +hugzChEnVxFpyptNqSVJNveVBkgll7DwJnwB36SFmN7t9gWsCmRhFB2GXenY+DgD +UKKe+egvpi9sq24i3p2rz4aMAPCpAy1d+0XiyeheIhlkJ+SnnOTt72rNSvrdSskB +xT9mW3k8CJgXc8nZEb+4LsKxJSQPMF7nJgyqvqbJ1+9W7UeaBAK0wKVrZShenfKZ +tr67fcW58a2H/Bp3kRgDnNzPkmnwWh0kDj72upshNKozYwlUOqa+U+zspFK1i/U7 +ccPGGrxCpbq0Jz94XZCEZdzUf2CcsrktoT8p7afXdFt/wQIDAQABMA0GCSqGSIb3 +DQEBBQUAA4IBAQBpVwxvy9Eec9QCDDA0rD+Yolkig8d0GlTuKjrk4wDjb8Tf7rQC +TvJz9wJMoXLJ0N433ha4uFQGrJMxLzuvlxYWTBGkuZwH4h2zau9PWz3UuMf+WIXa +e0rPPcg+FxY2MaT7GZBpiD6oI/W1ugVZqm2KdxWRfdQdBY6sN0QEFzrT3m+0JUrM +PGBLLKiKWXkzWy+YaDPN4E/5/V8d6N0Fjb2FTY/MO31PveydAPMrUgyivXOzEI7V +saek+sl65BER091wgPRQLz8SItyvzmAPGnCgb6FYI1rurN/VpEZbqYV9pOI7vCEU +bF6tvkgyw2nX/r9hGzyIuFG3cKQQbQbo6VK1 +-----END CERTIFICATE----- diff --git a/cas-server-support-x509/src/test/resources/x509-ldap-context.xml b/cas-server-support-x509/src/test/resources/x509-ldap-context.xml new file mode 100644 index 000000000000..cef1d46324b5 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/x509-ldap-context.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + certificateRevocationList + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-support-x509/src/test/resources/x509-san-upn-resolver.crt b/cas-server-support-x509/src/test/resources/x509-san-upn-resolver.crt new file mode 100644 index 000000000000..f46b79b24959 --- /dev/null +++ b/cas-server-support-x509/src/test/resources/x509-san-upn-resolver.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE6DCCA9CgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpzELMAkGA1UEBhMCVVMx +HTAbBgNVBAgMFERpc3RyaWN0IG9mIENvbHVtYmlhMRMwEQYDVQQHDApXYXNoaW5n +dG9uMRUwEwYDVQQKDAxTb21lIENvbXBhbnkxGDAWBgNVBAsMD1NvbWUgRGVwYXJ0 +bWVudDEQMA4GA1UEAwwHVGVzdCBDQTEhMB8GCSqGSIb3DQEJARYSY2FAc29tZWNv +bXBhbnkuY29tMB4XDTE0MDYxMjE3MDkxMloXDTI0MDYxMjE3MDkxMlowgZYxCzAJ +BgNVBAYTAlVTMR0wGwYDVQQIDBREaXN0cmljdCBvZiBDb2x1bWJpYTEVMBMGA1UE +CgwMU29tZSBDb21wYW55MRgwFgYDVQQLDA9Tb21lIERlcGFydG1lbnQxEjAQBgNV +BAMMCVRlc3QgVXNlcjEjMCEGCSqGSIb3DQEJARYUdGVzdEBzb21lY29tcGFueS5j +b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD42s0Q8YXv/rQrk4rF +4aHmuz8Tq9jWk3bU/tJoBgZTG2xCYfolT2z4j2Qa6kjXucEJuqOKihxMMZ1We0G4 +I6tm5QJxqkEoUYmUZHu/QZSrH1gwgS0yjvfq+Kk+yvKqplXDUyxbLRuMBRgFRCy0 +TUvdJPE4IQZQCcHir0Vqs667vj0UjSpI+y0BDZPY5CRePRSKcjM4ixoR9B8xj5kg +RcMxg4EszC2oK7z0IuuYKi0ZOdot1wVKP4OD/9evE2wjUYVeYCAV9y7tMlVsN0N5 +dRplCSIa/CA5gTMod3C92t83VoPqfb0f71cNQAsx1V3dNtOKnTOoG5jX70RR4Rqk +8ItNAgMBAAGjggEsMIIBKDAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAL +BgNVHQ8EBAMCBeAwKwYJYIZIAYb4QgENBB4WHFNtYXJ0IENhcmQgTG9naW4gQ2Vy +dGlmaWNhdGUwHQYDVR0OBBYEFN1P5EBNqZ+MGrJQziiVMhkKAXr9MB8GA1UdIwQY +MBaAFCHrFg422S+AWOHXfPIdqxbRhXegME4GA1UdEQRHMEWBFHRlc3RAc29tZWNv +bXBhbnkuY29toC0GCisGAQQBgjcUAgOgHwwddGVzdC11c2VyQHNvbWUtY29tcGFu +eS1kb21haW4wHQYDVR0SBBYwFIESY2FAc29tZWNvbXBhbnkuY29tMB8GA1UdJQQY +MBYGCCsGAQUFBwMCBgorBgEEAYI3FAICMA0GCSqGSIb3DQEBCwUAA4IBAQCPI5Zk +DqGHkKfFhRjlzLLajUEveggs74x3roi6S0zlpXpbPA3iZ2N8COf/QZL3twKunbP9 +XpmW/pcSji3+pil9aHPRn69S4cSuKdN5ZP9oQhkZxdk2UFS8Ts0WA+SUDJ3qTEtA +Q0HswBFBzWGOi0zkCtvAaBa8WSnDPtUN5RzmUtkKoMxBzu/MEMWNYXZyk/G2NHMJ +jh0N+ICpRNpnXGIZBwFymIRGH/PjtVkArVXy0hILWP/qfYzYMFgUBl0InyQzT0Hw +gzGdoeK7fVYrPWLK3ryRqqSR1XvfKFHwlnIg4XTBN4Cj7m5TmpntgVhO2JpNDjLr +K/NmtORHe6OhF17I +-----END CERTIFICATE----- diff --git a/cas-server-uber-webapp/NOTICE b/cas-server-uber-webapp/NOTICE new file mode 100644 index 000000000000..062c86b8ef9c --- /dev/null +++ b/cas-server-uber-webapp/NOTICE @@ -0,0 +1,142 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache Commons Logging under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apache Shiro :: Core under The Apache Software License, Version 2.0 + Apereo CAS - Uber WAR - DEPRECATED under Apache 2 + Apereo CAS Core under Apache 2 + Apereo CAS JBoss Cache Integration - DEPRECATED under Apache 2 + Apereo CAS JDBC Support under Apache 2 + Apereo CAS LDAP Support under Apache 2 + Apereo CAS Memcached Integration under Apache 2 + Apereo CAS RADIUS Support under Apache 2 + Apereo CAS SPNEGO/NTLM Support under Apache 2 + Apereo CAS Web Application under Apache 2 + Apereo CAS X.509 Client Certificate Support under Apache 2 + ASM Core under BSD + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + Bouncy Castle Provider under Bouncy Castle Licence + CDI APIs under Apache License, Version 2.0 + Commons BeanUtils under The Apache Software License, Version 2.0 + Commons Chain under The Apache Software License, Version 2.0 + Commons CLI under The Apache Software License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons Configuration under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Pool under The Apache Software License, Version 2.0 + commons-beanutils-core under Apache License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + Digester under The Apache Software License, Version 2.0 + dom4j under BSD License + ehcache under The Apache Software License, Version 2.0 + Ehcache Core under The Apache Software License, Version 2.0 + ehcache-terracotta under Terracotta Public License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + gnu-crypto under GNU General Public License, with the "library exception" + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Commons Development and Distribution License, Version 1.0 + java-getopt under GNU General Public License, with the "library exception" + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Cache - Core Edition under GNU Lesser General Public License + JBoss Common Classes under lgpl + JBoss Logging 3 under Apache License, version 2.0 + JBoss Logging Programming Interface under lgpl + jcifs under GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1 + JGroups under Library (or Lesser) GNU Public License 2.1 + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + jradius-core-1.1.3 under GNU Lessor/Library Public License, Version 3.0 or GNU Public License, Version 3.0 + jradius-dictionary-1.1.3 under GNU Lessor/Library Public License, Version 3.0 or GNU Public License, Version 3.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + Kryo under New BSD License + kryo serializers under The Apache Software License, Version 2.0 + Lang under The Apache Software License, Version 2.0 + LDAPTIVE under Apache 2 or GNU Lesser General Public License + Metrics Core under Apache License 2.0 + MinLog under New BSD License + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + org.samba.jcifs:jcifs-ext under GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + ReflectASM under New BSD License + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + SLF4J LOG4J-12 Binding under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + Spymemcached under The Apache Software License, Version 2.0 + terracotta-toolkit-runtime under Terracotta Public License + VT Crypt Library under Apache 2 or GNU Lesser General Public License + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-uber-webapp/pom.xml b/cas-server-uber-webapp/pom.xml new file mode 100644 index 000000000000..82606e91d43d --- /dev/null +++ b/cas-server-uber-webapp/pom.xml @@ -0,0 +1,112 @@ + + + + + + cas-server + org.jasig.cas + 4.1.0-SNAPSHOT + + 4.0.0 + war + Apereo CAS - Uber WAR - DEPRECATED + + The CAS uber-webapp module is deprecated and will be removed from subsequent CAS releases. + You should not attempt to include this module in your CAS Maven overlays, and rather try to use the + cas-server-webapp + module instead, and include dependencies that are required for your build. + + cas-server-uber-webapp + + + + + maven-war-plugin + + cas + + + + + + + + org.jasig.cas + cas-server-webapp + ${project.version} + war + runtime + + + + org.jasig.cas + cas-server-support-jdbc + ${project.version} + runtime + + + + org.jasig.cas + cas-server-support-radius + ${project.version} + runtime + + + + org.jasig.cas + cas-server-support-ldap + ${project.version} + runtime + + + + org.jasig.cas + cas-server-support-x509 + ${project.version} + runtime + + + + org.jasig.cas + cas-server-support-spnego + ${project.version} + runtime + + + + org.jasig.cas + cas-server-integration-jboss + ${project.version} + runtime + + + + org.jasig.cas + cas-server-integration-memcached + ${project.version} + runtime + + + + + ${project.parent.basedir} + + diff --git a/cas-server-uber-webapp/src/main/webapp/web.xml b/cas-server-uber-webapp/src/main/webapp/web.xml new file mode 100644 index 000000000000..131178c2f3fb --- /dev/null +++ b/cas-server-uber-webapp/src/main/webapp/web.xml @@ -0,0 +1,32 @@ + + + + + + The CAS uber-webapp module is deprecated and will be removed from subsequent CAS releases. + You should not attempt to include this module in your CAS Maven overlays, and rather try to use the cas-server-webapp + module instead, and include dependencies that are required for your build. + + diff --git a/cas-server-uber-webapp/src/site/site.xml b/cas-server-uber-webapp/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-uber-webapp/src/site/site.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/cas-server-webapp-support/.gitignore b/cas-server-webapp-support/.gitignore new file mode 100644 index 000000000000..ea8c4bf7f35f --- /dev/null +++ b/cas-server-webapp-support/.gitignore @@ -0,0 +1 @@ +/target diff --git a/cas-server-webapp-support/NOTICE b/cas-server-webapp-support/NOTICE new file mode 100644 index 000000000000..c30ab099f54b --- /dev/null +++ b/cas-server-webapp-support/NOTICE @@ -0,0 +1,131 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apache Log4j Web under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS Generic Support under Apache 2 + Apereo CAS Web Application support under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + Bouncy Castle Provider under Bouncy Castle Licence + c3p0:JDBC DataSources/Resource Pools under GNU LESSER GENERAL PUBLIC LICENSE + CDI APIs under Apache License, Version 2.0 + ClassMate under The Apache Software License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Commons Logging under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + Cryptacular Library under Apache 2 or GNU Lesser General Public License + dom4j under BSD License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Hibernate JPA Support under GNU Lesser General Public License + Hibernate Validator Engine under Apache License, Version 2.0 + HyperSQL Database under HSQLDB License, a BSD open source license + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Inspektr - Error Logging under Apache 2.0 License + Inspektr - Spring Framework Support under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson Integration for Metrics under Apache License 2.0 + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + JBoss Logging I18n Annotations under Public Domain + JCL 1.1.1 implemented over SLF4J under MIT License + jersey-core under CDDL 1.1 or GPL2 w/ CPE + jersey-server under CDDL 1.1 or GPL2 w/ CPE + jersey-servlet under CDDL 1.1 or GPL2 w/ CPE + jersey-spring under CDDL 1.1 or GPL2 w/ CPE + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + jsr311-api under CDDL License + jstl under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + JVM Integration for Metrics under Apache License 2.0 + Metrics Core under Apache License 2.0 + Metrics Health Checks under Apache License 2.0 + Metrics Utility Servlets under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + quartz under The Apache Software License, Version 2.0 + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + Spring Webflow Client Repository under Apache 2 + spring-security-config under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + spring-security-web under The Apache Software License, Version 2.0 + Stax2 API under The BSD License + Streaming API for XML under Sun Binary Code License + Woodstox under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-webapp-support/pom.xml b/cas-server-webapp-support/pom.xml new file mode 100644 index 000000000000..180167872405 --- /dev/null +++ b/cas-server-webapp-support/pom.xml @@ -0,0 +1,182 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-webapp-support + jar + Apereo CAS Web Application support + + + + org.mockito + mockito-all + ${mockito.version} + test + jar + + + + org.jasig.inspektr + inspektr-support-spring + runtime + + + + org.springframework.security + spring-security-core + compile + + + + org.springframework.security + spring-security-web + compile + + + + org.springframework.security + spring-security-config + runtime + + + + org.springframework + spring-aop + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + org.jasig.cas + cas-server-support-generic + ${project.version} + runtime + + + + org.springframework + spring-context-support + compile + + + + org.springframework + spring-expression + compile + + + + org.hibernate + hibernate-validator + runtime + + + + org.codehaus.woodstox + woodstox-core-asl + + + + org.hibernate + hibernate-entitymanager + test + + + + org.jasig.cas + cas-server-core + ${project.version} + test-jar + test + + + + org.hsqldb + hsqldb + test + + + + javax.servlet + jstl + + + + org.quartz-scheduler + quartz + compile + + + + org.jasig + spring-webflow-client-repo + + + slf4j-log4j12 + org.slf4j + + + + + + io.dropwizard.metrics + metrics-servlets + compile + + + + com.sun.jersey + jersey-core + + + + com.sun.jersey + jersey-server + + + + com.sun.jersey + jersey-servlet + + + + com.sun.jersey.contribs + jersey-spring + + + + org.apache.logging.log4j + log4j-web + + + + + ${project.parent.basedir} + + diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/services/web/RegisteredServiceThemeBasedViewResolver.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/services/web/RegisteredServiceThemeBasedViewResolver.java new file mode 100644 index 000000000000..327eec7144a8 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/services/web/RegisteredServiceThemeBasedViewResolver.java @@ -0,0 +1,146 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services.web; + +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.web.support.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.view.AbstractUrlBasedView; +import org.springframework.web.servlet.view.InternalResourceView; +import org.springframework.web.servlet.view.InternalResourceViewResolver; +import org.springframework.webflow.execution.RequestContext; +import org.springframework.webflow.execution.RequestContextHolder; + +/** + * {@link RegisteredServiceThemeBasedViewResolver} is an alternate Spring View Resolver that utilizes a service's + * associated theme to selectively choose which set of UI views will be used to generate + * the standard views (casLoginView.jsp, casLogoutView.jsp etc). + * + *

Views associated with a particular theme by default are expected to be found at: + * {@link #DEFAULT_PATH_PREFIX}/themeId/ui. A starting point may be to + * clone the default set of view pages into a new directory based on the theme id.

+ * + *

Note: There also exists a {@link org.jasig.cas.services.web.ServiceThemeResolver} + * that attempts to resolve the view name based on the service theme id. The difference + * however is that {@link org.jasig.cas.services.web.ServiceThemeResolver} only decorates + * the default view pages with additional tags and coloring, such as CSS and JS. The + * component presented here on the other hand has the ability to load an entirely new + * set of pages that are may be totally different from that of the default's. This + * is specially useful in cases where the set of pages for a theme that are targetted + * for a different type of audience are entirely different structurally that simply + * using the {@link org.jasig.cas.services.web.ServiceThemeResolver} is not practical + * to augment the default views. In such cases, new view pages may be required.

+ * + * @author John Gasper + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class RegisteredServiceThemeBasedViewResolver extends InternalResourceViewResolver { + private static final Logger LOGGER = LoggerFactory.getLogger(RegisteredServiceThemeBasedViewResolver.class); + private static final String DEFAULT_PATH_PREFIX = "/WEB-INF/view/jsp"; + + /** The ServiceRegistry to look up the service. */ + private final ServicesManager servicesManager; + + private final String defaultThemeId; + + private String pathPrefix = DEFAULT_PATH_PREFIX; + + /** + * The {@link RegisteredServiceThemeBasedViewResolver} constructor. + * @param defaultThemeId the theme to apply if the service doesn't specific one or a service is not provided + * @param servicesManager the serviceManager implementation + * @see #setCache(boolean) + */ + public RegisteredServiceThemeBasedViewResolver(final String defaultThemeId, final ServicesManager servicesManager) { + super(); + super.setCache(false); + + this.defaultThemeId = defaultThemeId; + this.servicesManager = servicesManager; + } + + /** + * Uses the viewName and the theme associated with the service. + * being requested and returns the appropriate view. + * @param viewName the name of the view to be resolved + * @return a theme-based UrlBasedView + * @throws Exception an exception + */ + @Override + protected AbstractUrlBasedView buildView(final String viewName) throws Exception { + final RequestContext requestContext = RequestContextHolder.getRequestContext(); + final WebApplicationService service = WebUtils.getService(requestContext); + final RegisteredService registeredService = this.servicesManager.findServiceBy(service); + + final String themeId = service != null && registeredService != null + && registeredService.getAccessStrategy().isServiceAccessAllowed() + && StringUtils.hasText(registeredService.getTheme()) ? registeredService.getTheme() : defaultThemeId; + + final String themePrefix = String.format("%s/%s/ui/", pathPrefix, themeId); + LOGGER.debug("Prefix {} set for service {} with theme {}", themePrefix, service, themeId); + + //Build up the view like the base classes do, but we need to forcefully set the prefix for each request. + //From UrlBasedViewResolver.buildView + final InternalResourceView view = (InternalResourceView) BeanUtils.instantiateClass(getViewClass()); + view.setUrl(themePrefix + viewName + getSuffix()); + final String contentType = getContentType(); + if (contentType != null) { + view.setContentType(contentType); + } + view.setRequestContextAttribute(getRequestContextAttribute()); + view.setAttributesMap(getAttributesMap()); + + //From InternalResourceViewResolver.buildView + view.setAlwaysInclude(false); + view.setExposeContextBeansAsAttributes(false); + view.setPreventDispatchLoop(true); + + LOGGER.debug("View resolved: {}", view.getUrl()); + + return view; + } + + /** + * setCache is not supported in the {@link RegisteredServiceThemeBasedViewResolver} because each + * request must be independently evaluated. + * @param cache a value indicating whether the view should cache results. + */ + @Override + public void setCache(final boolean cache) { + LOGGER.warn("The {} does not support caching. Turned off caching forcefully.", this.getClass().getSimpleName()); + super.setCache(false); + } + + /** + * Sets path prefix. This is the location where + * views are expected to be found. The default + * prefix is {@link #DEFAULT_PATH_PREFIX}. + * + * @param pathPrefix the path prefix + */ + public void setPathPrefix(final String pathPrefix) { + this.pathPrefix = pathPrefix; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/services/web/ServiceThemeResolver.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/services/web/ServiceThemeResolver.java new file mode 100644 index 000000000000..b0d163c5fa64 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/services/web/ServiceThemeResolver.java @@ -0,0 +1,141 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services.web; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.web.support.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.support.ResourceBundleMessageSource; +import org.springframework.web.servlet.theme.AbstractThemeResolver; +import org.springframework.webflow.execution.RequestContext; +import org.springframework.webflow.execution.RequestContextHolder; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.regex.Pattern; + +/** + * ThemeResolver to determine the theme for CAS based on the service provided. + * The theme resolver will extract the service parameter from the Request object + * and attempt to match the URL provided to a Service Id. If the service is + * found, the theme associated with it will be used. If not, these is associated + * with the service or the service was not found, a default theme will be used. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public final class ServiceThemeResolver extends AbstractThemeResolver { + + private static final Logger LOGGER = LoggerFactory.getLogger(ServiceThemeResolver.class); + + /** The ServiceRegistry to look up the service. */ + private ServicesManager servicesManager; + + private Map overrides = new HashMap<>(); + + @Override + public String resolveThemeName(final HttpServletRequest request) { + if (this.servicesManager == null) { + return getDefaultThemeName(); + } + // retrieve the user agent string from the request + final String userAgent = request.getHeader("User-Agent"); + + if (StringUtils.isBlank(userAgent)) { + return getDefaultThemeName(); + } + + for (final Map.Entry entry : this.overrides.entrySet()) { + if (entry.getKey().matcher(userAgent).matches()) { + request.setAttribute("isMobile", "true"); + request.setAttribute("browserType", entry.getValue()); + break; + } + } + + final RequestContext context = RequestContextHolder.getRequestContext(); + final Service service = WebUtils.getService(context); + if (service != null) { + final RegisteredService rService = this.servicesManager.findServiceBy(service); + if (rService != null && rService.getAccessStrategy().isServiceAccessAllowed() + && StringUtils.isNotBlank(rService.getTheme())) { + LOGGER.debug("Service [{}] is configured to use a custom theme [{}]", rService, rService.getTheme()); + final CasThemeResourceBundleMessageSource messageSource = new CasThemeResourceBundleMessageSource(); + messageSource.setBasename(rService.getTheme()); + if (messageSource.doGetBundle(rService.getTheme(), request.getLocale()) != null) { + LOGGER.debug("Found custom theme [{}] for service [{}]", rService.getTheme(), rService); + return rService.getTheme(); + } else { + LOGGER.warn("Custom theme {} for service {} cannot be located. Falling back to default theme...", + rService.getTheme(), rService); + } + } + } + return getDefaultThemeName(); + } + + @Override + public void setThemeName(final HttpServletRequest request, final HttpServletResponse response, final String themeName) { + // nothing to do here + } + + public void setServicesManager(final ServicesManager servicesManager) { + this.servicesManager = servicesManager; + } + + /** + * Sets the map of mobile browsers. This sets a flag on the request called "isMobile" and also + * provides the custom flag called browserType which can be mapped into the theme. + *

+ * Themes that understand isMobile should provide an alternative stylesheet. + * + * @param mobileOverrides the list of mobile browsers. + */ + public void setMobileBrowsers(final Map mobileOverrides) { + // initialize the overrides variable to an empty map + this.overrides = new HashMap<>(); + + for (final Map.Entry entry : mobileOverrides.entrySet()) { + this.overrides.put(Pattern.compile(entry.getKey()), entry.getValue()); + } + } + + private static class CasThemeResourceBundleMessageSource extends ResourceBundleMessageSource { + @Override + protected ResourceBundle doGetBundle(final String basename, final Locale locale) { + try { + final ResourceBundle bundle = ResourceBundle.getBundle(basename, locale, getBundleClassLoader()); + if (bundle != null && bundle.keySet().size() > 0) { + return bundle; + } + } catch (final Exception e) { + LOGGER.debug(e.getMessage(), e); + } + return null; + } + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/util/AutowiringSchedulerFactoryBean.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/util/AutowiringSchedulerFactoryBean.java new file mode 100644 index 000000000000..59a402ce1f4b --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/util/AutowiringSchedulerFactoryBean.java @@ -0,0 +1,59 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.quartz.Trigger; +import org.springframework.scheduling.quartz.SchedulerFactoryBean; + +import java.util.Map; + +/** + * Extension of {@link SchedulerFactoryBean} that collects trigger bean + * definitions from the application context and calls + * {@link #setTriggers(org.quartz.Trigger[])} to autowire triggers at + * {@link #afterPropertiesSet()} time. + * + * @author Marvin S. Addison + * @author Scott Battaglia + * @since 3.3.4 + **/ +public final class AutowiringSchedulerFactoryBean extends SchedulerFactoryBean { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private ApplicationContext applicationContext; + + @Override + public void afterPropertiesSet() throws Exception { + final Map triggers = this.applicationContext.getBeansOfType(Trigger.class); + super.setTriggers(triggers.values().toArray(new Trigger[triggers.size()])); + + logger.debug("Autowired the following triggers defined in application context: {}", triggers.keySet().toString()); + super.afterPropertiesSet(); + } + + @Override + public void setApplicationContext(final ApplicationContext applicationContext) { + super.setApplicationContext(applicationContext); + this.applicationContext = applicationContext; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/util/CasLoggerContextInitializer.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/util/CasLoggerContextInitializer.java new file mode 100644 index 000000000000..ab44f1c298b9 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/util/CasLoggerContextInitializer.java @@ -0,0 +1,165 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.util; + +import org.apache.commons.lang3.StringUtils; +import org.reflections.Reflections; +import org.reflections.scanners.SubTypesScanner; +import org.reflections.util.ClasspathHelper; +import org.reflections.util.ConfigurationBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; +import org.springframework.web.context.ServletContextAware; + +import javax.annotation.PreDestroy; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.validation.constraints.NotNull; +import java.net.URL; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Initializes the CAS logging framework by calling + * the logger initializer and sets the location of the + * log configuration file. + * @author Misagh Moayyed + * @since 4.1 + */ +@Component +public final class CasLoggerContextInitializer implements ServletContextAware { + private static AtomicBoolean INITIALIZED = new AtomicBoolean(false); + + private static final Logger LOGGER = LoggerFactory.getLogger(CasLoggerContextInitializer.class); + + private ServletContext context; + + private ServletContextListener loggerContext; + + private final String loggerContextPackageName; + + private final Resource logConfigurationFile; + + private final String logConfigurationField; + + /** + * Instantiates a new Cas logger context initializer. + */ + protected CasLoggerContextInitializer() { + this.loggerContext = null; + this.loggerContextPackageName = null; + this.logConfigurationField = null; + this.logConfigurationFile = null; + } + + /** + * Instantiates a new Cas logger context initializer. + * + * @param loggerContextPackageName the logger context package name + * @param logConfigurationFile the log configuration file + * @param logConfigurationField the log configuration field + */ + public CasLoggerContextInitializer(@NotNull final String loggerContextPackageName, + @NotNull final Resource logConfigurationFile, + @NotNull final String logConfigurationField) { + this.loggerContextPackageName = loggerContextPackageName; + this.logConfigurationField = logConfigurationField; + this.logConfigurationFile = logConfigurationFile; + } + + /** + * Initialize the logger by decorating the context + * with settings for the log file and context. + * Calls the initializer of the logging framework + * to start the logger. + */ + private void initialize() { + try { + if (!INITIALIZED.get() && this.loggerContext != null) { + final ServletContextEvent event = new ServletContextEvent(this.context); + this.loggerContext.contextInitialized(event); + LOGGER.debug("Initialized logging context via [{}]. Logs will be written to [{}]", + this.loggerContext.getClass().getSimpleName(), + this.logConfigurationFile); + INITIALIZED.set(true); + } + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Destroys all logging hooks and shuts down + * the logger. + */ + @PreDestroy + public void destroy() { + try { + if (INITIALIZED.get() && this.loggerContext != null) { + final ServletContextEvent event = new ServletContextEvent(this.context); + LOGGER.debug("Destroying logging context and shutting it down"); + this.loggerContext.contextDestroyed(event); + } + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Prepares the logger context. Locates the context and + * sets the configuration file. + * @return the logger context + */ + private ServletContextListener prepareAndgetContextListener() { + try { + if (StringUtils.isNotBlank(this.loggerContextPackageName)) { + final Collection set = ClasspathHelper.forPackage(this.loggerContextPackageName); + final Reflections reflections = new Reflections(new ConfigurationBuilder().addUrls(set).setScanners(new SubTypesScanner())); + final Set> subTypesOf = reflections.getSubTypesOf(ServletContextListener.class); + final ServletContextListener loggingContext = subTypesOf.iterator().next().newInstance(); + this.context.setInitParameter(this.logConfigurationField, this.logConfigurationFile.getURI().toString()); + return loggingContext; + } + return null; + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + /** + * {@inheritDoc} + *

Prepared the logger context with the + * received servlet web context. Because the context + * may be initialized twice, there are safety checks + * added to ensure we don't create duplicate log + * environments.

+ * @param servletContext + */ + @Override + public void setServletContext(final ServletContext servletContext) { + this.context = servletContext; + this.loggerContext = prepareAndgetContextListener(); + initialize(); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/DelegatingController.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/DelegatingController.java new file mode 100644 index 000000000000..ce4534a82382 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/DelegatingController.java @@ -0,0 +1,99 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web; + +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.AbstractController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * Delegating controller. + * Tries to find a controller among its delegates, that can handle the current request. + * If none is found, an error is generated. + * @author Frederic Esnault + * @since 3.5 + */ +public class DelegatingController extends AbstractController { + + /** View if Service Ticket Validation Fails. */ + private static final String DEFAULT_ERROR_VIEW_NAME = "casServiceFailureView"; + + private List delegates; + + /** The view to redirect if no delegate can handle the request. */ + @NotNull + private String failureView = DEFAULT_ERROR_VIEW_NAME; + + /** + * Handles the request. + * Ask all delegates if they can handle the current request. + * The first to answer true is elected as the delegate that will process the request. + * If no controller answers true, we redirect to the error page. + * @param request the request to handle + * @param response the response to write to + * @return the model and view object + * @throws Exception if an error occurs during request handling + */ + @Override + protected final ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + for (final DelegateController delegate : delegates) { + if (delegate.canHandle(request, response)) { + return delegate.handleRequest(request, response); + } + } + return generateErrorView("INVALID_REQUEST", "INVALID_REQUEST", null); + } + + /** + * Generate error view based on {@link #setFailureView(String)}. + * + * @param code the code + * @param description the description + * @param args the args + * @return the model and view + */ + private ModelAndView generateErrorView(final String code, final String description, final Object[] args) { + final ModelAndView modelAndView = new ModelAndView(this.failureView); + final String convertedDescription = getMessageSourceAccessor().getMessage(description, args, description); + modelAndView.addObject("code", code); + modelAndView.addObject("description", convertedDescription); + + return modelAndView; + } + + /** + * @param delegates the delegate controllers to set + */ + @NotNull + public void setDelegates(final List delegates) { + this.delegates = delegates; + } + + /** + * @param failureView The failureView to set. + */ + public final void setFailureView(final String failureView) { + this.failureView = failureView; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/FlowExecutionExceptionResolver.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/FlowExecutionExceptionResolver.java new file mode 100644 index 000000000000..4667c9c950f0 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/FlowExecutionExceptionResolver.java @@ -0,0 +1,91 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web; + +import org.apache.commons.lang3.StringEscapeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.view.RedirectView; +import org.springframework.webflow.execution.repository.BadlyFormattedFlowExecutionKeyException; +import org.springframework.webflow.execution.repository.FlowExecutionRepositoryException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; +import java.util.HashMap; +import java.util.Map; + +/** + * The FlowExecutionExceptionResolver catches the FlowExecutionRepositoryException + * thrown by Spring Webflow when the given flow id no longer exists. This can + * occur if a particular flow has reached an end state (the id is no longer + * valid) + *

+ * It will redirect back to the requested URI which should start a new workflow. + *

+ * + * @author Scott Battaglia + * @author Misagh Moayyed + * @since 3.0.0 + */ +public final class FlowExecutionExceptionResolver implements HandlerExceptionResolver { + + /** Instance of a logger. */ + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @NotNull + private String modelKey = "exception.message"; + + @Override + public ModelAndView resolveException(final HttpServletRequest request, + final HttpServletResponse response, final Object handler, + final Exception exception) { + + /* + * Since FlowExecutionRepositoryException is a common ancestor to these exceptions and other + * error cases we would likely want to hide from the user, it seems reasonable to check for + * FlowExecutionRepositoryException. + * + * BadlyFormattedFlowExecutionKeyException is specifically ignored by this handler + * because redirecting to the requested URI with this exception may cause an infinite + * redirect loop (i.e. when invalid "execution" parameter exists as part of the query string + */ + if (!(exception instanceof FlowExecutionRepositoryException) + || exception instanceof BadlyFormattedFlowExecutionKeyException) { + logger.debug("Ignoring the received exception due to a type mismatch", exception); + return null; + } + + final String urlToRedirectTo = request.getRequestURI() + + (request.getQueryString() != null ? '?' + + request.getQueryString() : ""); + + logger.debug("Error getting flow information for URL [{}]", urlToRedirectTo, exception); + final Map model = new HashMap<>(); + model.put(this.modelKey, StringEscapeUtils.escapeHtml4(exception.getMessage())); + + return new ModelAndView(new RedirectView(urlToRedirectTo), model); + } + + public void setModelKey(final String modelKey) { + this.modelKey = modelKey; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/ProxyController.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/ProxyController.java new file mode 100644 index 000000000000..c700d418003c --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/ProxyController.java @@ -0,0 +1,138 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; + +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.services.UnauthorizedServiceException; +import org.jasig.cas.ticket.TicketException; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.AbstractController; + +/** + * The ProxyController is involved with returning a Proxy Ticket (in CAS 2 + * terms) to the calling application. In CAS 3, a Proxy Ticket is just a Service + * Ticket granted to a service. + *

+ * The ProxyController requires the following property to be set: + *

+ *
    + *
  • centralAuthenticationService - the service layer
  • + *
  • casArgumentExtractor - the assistant for extracting parameters
  • + *
+ * + * @author Scott Battaglia + * @since 3.0.0 + */ +public final class ProxyController extends AbstractController { + + /** View for if the creation of a "Proxy" Ticket Fails. */ + private static final String CONST_PROXY_FAILURE = "cas2ProxyFailureView"; + + /** View for if the creation of a "Proxy" Ticket Succeeds. */ + private static final String CONST_PROXY_SUCCESS = "cas2ProxySuccessView"; + + /** Key to use in model for service tickets. */ + private static final String MODEL_SERVICE_TICKET = "ticket"; + + /** CORE to delegate all non-web tier functionality to. */ + @NotNull + private CentralAuthenticationService centralAuthenticationService; + + /** + * Instantiates a new proxy controller, with cache seconds set to 0. + */ + public ProxyController() { + setCacheSeconds(0); + } + + /** + * {@inheritDoc} + * @return ModelAndView containing a view name of either + * casProxyFailureView or casProxySuccessView + */ + @Override + protected ModelAndView handleRequestInternal( + final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + final String ticket = request.getParameter("pgt"); + final Service targetService = getTargetService(request); + + if (!StringUtils.hasText(ticket) || targetService == null) { + return generateErrorView("INVALID_REQUEST", + "INVALID_REQUEST_PROXY", null); + } + + try { + return new ModelAndView(CONST_PROXY_SUCCESS, MODEL_SERVICE_TICKET, + this.centralAuthenticationService.grantServiceTicket(ticket, + targetService)); + } catch (final TicketException e) { + return generateErrorView(e.getCode(), e.getCode(), + new Object[] {ticket}); + } catch (final UnauthorizedServiceException e) { + return generateErrorView("UNAUTHORIZED_SERVICE", + "UNAUTHORIZED_SERVICE_PROXY", new Object[] {targetService}); + } + } + + /** + * Gets the target service from the request. + * + * @param request the request + * @return the target service + */ + private Service getTargetService(final HttpServletRequest request) { + return SimpleWebApplicationServiceImpl.createServiceFrom(request); + } + + /** + * Generate error view stuffing the code and description + * of the error into the model. View name is set to {@link #CONST_PROXY_FAILURE}. + * + * @param code the code + * @param description the description + * @param args the msg args + * @return the model and view + */ + private ModelAndView generateErrorView(final String code, + final String description, final Object[] args) { + final ModelAndView modelAndView = new ModelAndView(CONST_PROXY_FAILURE); + modelAndView.addObject("code", code); + modelAndView.addObject("description", getMessageSourceAccessor() + .getMessage(description, args, description)); + + return modelAndView; + } + + /** + * @param centralAuthenticationService The centralAuthenticationService to + * set. + */ + public void setCentralAuthenticationService( + final CentralAuthenticationService centralAuthenticationService) { + this.centralAuthenticationService = centralAuthenticationService; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/ServiceValidateController.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/ServiceValidateController.java new file mode 100644 index 000000000000..d9c1cb835b90 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/ServiceValidateController.java @@ -0,0 +1,370 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web; + +import org.jasig.cas.CasProtocolConstants; +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.AuthenticationException; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.HttpBasedServiceCredential; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.services.UnauthorizedProxyingException; +import org.jasig.cas.services.UnauthorizedServiceException; +import org.jasig.cas.ticket.TicketException; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketValidationException; +import org.jasig.cas.ticket.proxy.ProxyHandler; +import org.jasig.cas.validation.Assertion; +import org.jasig.cas.validation.Cas20ProtocolValidationSpecification; +import org.jasig.cas.validation.ValidationSpecification; +import org.jasig.cas.web.support.ArgumentExtractor; +import org.jasig.cas.web.view.CasViewConstants; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.ServletRequestDataBinder; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; +import java.net.URL; +import java.util.Collections; +import java.util.Map; + +/** + * Process the /validate , /serviceValidate , and /proxyValidate URL requests. + *

+ * Obtain the Service Ticket and Service information and present them to the CAS + * validation services. Receive back an Assertion containing the user Principal + * and (possibly) a chain of Proxy Principals. Store the Assertion in the Model + * and chain to a View to generate the appropriate response (CAS 1, CAS 2 XML, + * SAML, ...). + * + * @author Scott Battaglia + * @author Misagh Moayyed + * @since 3.0.0 + */ +public class ServiceValidateController extends DelegateController { + /** View if Service Ticket Validation Fails. */ + public static final String DEFAULT_SERVICE_FAILURE_VIEW_NAME = "cas2ServiceFailureView"; + + /** View if Service Ticket Validation Succeeds. */ + public static final String DEFAULT_SERVICE_SUCCESS_VIEW_NAME = "cas2ServiceSuccessView"; + + /** Implementation of Service Manager. */ + @NotNull + private ServicesManager servicesManager; + + /** The CORE which we will delegate all requests to. */ + @NotNull + private CentralAuthenticationService centralAuthenticationService; + + /** The validation protocol we want to use. */ + @NotNull + private Class validationSpecificationClass = Cas20ProtocolValidationSpecification.class; + + /** The proxy handler we want to use with the controller. */ + @NotNull + private ProxyHandler proxyHandler; + + /** The view to redirect to on a successful validation. */ + @NotNull + private String successView = DEFAULT_SERVICE_SUCCESS_VIEW_NAME; + + /** The view to redirect to on a validation failure. */ + @NotNull + private String failureView = DEFAULT_SERVICE_FAILURE_VIEW_NAME; + + /** Extracts parameters from Request object. */ + @NotNull + private ArgumentExtractor argumentExtractor; + + /** + * Overrideable method to determine which credentials to use to grant a + * proxy granting ticket. Default is to use the pgtUrl. + * + * @param service the webapp service requesting proxy + * @param request the HttpServletRequest object. + * @return the credentials or null if there was an error or no credentials + * provided. + */ + protected Credential getServiceCredentialsFromRequest(final WebApplicationService service, final HttpServletRequest request) { + final String pgtUrl = request.getParameter(CasProtocolConstants.PARAMETER_PROXY_CALLBACK_URL); + if (StringUtils.hasText(pgtUrl)) { + try { + final RegisteredService registeredService = this.servicesManager.findServiceBy(service); + verifyRegisteredServiceProperties(registeredService, service); + return new HttpBasedServiceCredential(new URL(pgtUrl), registeredService); + } catch (final Exception e) { + logger.error("Error constructing pgtUrl", e); + } + } + + return null; + } + + /** + * Inits the binder with the required fields. renew is required. + * + * @param request the request + * @param binder the binder + */ + protected void initBinder(final HttpServletRequest request, final ServletRequestDataBinder binder) { + binder.setRequiredFields("renew"); + } + + @Override + protected final ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + final WebApplicationService service = this.argumentExtractor.extractService(request); + final String serviceTicketId = service != null ? service.getArtifactId() : null; + + if (service == null || serviceTicketId == null) { + logger.debug("Could not identify service and/or service ticket for service: [{}]", service); + return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_REQUEST, + CasProtocolConstants.ERROR_CODE_INVALID_REQUEST, null); + } + + try { + final Credential serviceCredential = getServiceCredentialsFromRequest(service, request); + TicketGrantingTicket proxyGrantingTicketId = null; + + if (serviceCredential != null) { + try { + proxyGrantingTicketId = this.centralAuthenticationService.delegateTicketGrantingTicket(serviceTicketId, + serviceCredential); + logger.debug("Generated PGT [{}] off of service ticket [{}] and credential [{}]", + proxyGrantingTicketId.getId(), serviceTicketId, serviceCredential); + } catch (final AuthenticationException e) { + logger.info("Failed to authenticate service credential {}", serviceCredential); + } catch (final TicketException e) { + logger.error("Failed to create proxy granting ticket for {}", serviceCredential, e); + } + + if (proxyGrantingTicketId == null) { + return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK, + CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK, + new Object[] {serviceCredential.getId()}); + } + } + + final Assertion assertion = this.centralAuthenticationService.validateServiceTicket(serviceTicketId, service); + + final ValidationSpecification validationSpecification = this.getCommandClass(); + final ServletRequestDataBinder binder = new ServletRequestDataBinder(validationSpecification, "validationSpecification"); + initBinder(request, binder); + binder.bind(request); + + if (!validationSpecification.isSatisfiedBy(assertion)) { + logger.debug("Service ticket [{}] does not satisfy validation specification.", serviceTicketId); + return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_TICKET, + CasProtocolConstants.ERROR_CODE_INVALID_TICKET, null); + } + + String proxyIou = null; + if (serviceCredential != null && this.proxyHandler.canHandle(serviceCredential)) { + proxyIou = this.proxyHandler.handle(serviceCredential, proxyGrantingTicketId); + if (StringUtils.isEmpty(proxyIou)) { + return generateErrorView(CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK, + CasProtocolConstants.ERROR_CODE_INVALID_PROXY_CALLBACK, + new Object[] {serviceCredential.getId()}); + } + } + + onSuccessfulValidation(serviceTicketId, assertion); + logger.debug("Successfully validated service ticket {} for service [{}]", serviceTicketId, service.getId()); + return generateSuccessView(assertion, proxyIou, service, proxyGrantingTicketId); + } catch (final TicketValidationException e) { + final String code = e.getCode(); + return generateErrorView(code, code, + new Object[] {serviceTicketId, e.getOriginalService().getId(), service.getId()}); + } catch (final TicketException te) { + return generateErrorView(te.getCode(), te.getCode(), + new Object[] {serviceTicketId}); + } catch (final UnauthorizedProxyingException e) { + return generateErrorView(e.getMessage(), e.getMessage(), new Object[] {service.getId()}); + } catch (final UnauthorizedServiceException e) { + return generateErrorView(e.getMessage(), e.getMessage(), null); + } + } + + /** + * Triggered on successful validation events. Extensions are to + * use this as hook to plug in behvior. + * + * @param serviceTicketId the service ticket id + * @param assertion the assertion + */ + protected void onSuccessfulValidation(final String serviceTicketId, final Assertion assertion) { + // template method with nothing to do. + } + + /** + * Generate error view, set to {@link #setFailureView(String)}. + * + * @param code the code + * @param description the description + * @param args the args + * @return the model and view + */ + private ModelAndView generateErrorView(final String code, final String description, final Object[] args) { + final ModelAndView modelAndView = new ModelAndView(this.failureView); + final String convertedDescription = getMessageSourceAccessor().getMessage(description, args, description); + modelAndView.addObject("code", code); + modelAndView.addObject("description", convertedDescription); + + return modelAndView; + } + + /** + * Generate the success view. The result will contain the assertion and the proxy iou. + * + * @param assertion the assertion + * @param proxyIou the proxy iou + * @param service the validated service + * @param proxyGrantingTicket the proxy granting ticket + * @return the model and view, pointed to the view name set by + */ + private ModelAndView generateSuccessView(final Assertion assertion, final String proxyIou, + final WebApplicationService service, + final TicketGrantingTicket proxyGrantingTicket) { + + final ModelAndView success = new ModelAndView(this.successView); + success.addObject(CasViewConstants.MODEL_ATTRIBUTE_NAME_ASSERTION, assertion); + success.addObject(CasViewConstants.MODEL_ATTRIBUTE_NAME_SERVICE, service); + success.addObject(CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET_IOU, proxyIou); + if (proxyGrantingTicket != null) { + success.addObject(CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET, proxyGrantingTicket.getId()); + } + final Map augmentedModelObjects = augmentSuccessViewModelObjects(assertion); + if (augmentedModelObjects != null) { + success.addAllObjects(augmentedModelObjects); + } + return success; + } + + /** + * Augment success view model objects. Provides + * a way for extension of this controller to dynamically + * populate the model object with attributes + * that describe a custom nature of the validation protocol. + * + * @param assertion the assertion + * @return map of objects each keyed to a name + */ + protected Map augmentSuccessViewModelObjects(final Assertion assertion) { + return Collections.emptyMap(); + } + + /** + * Gets the command class based on {@link #setValidationSpecificationClass(Class)}. + * + * @return the command class + */ + private ValidationSpecification getCommandClass() { + try { + return (ValidationSpecification) this.validationSpecificationClass.newInstance(); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canHandle(final HttpServletRequest request, final HttpServletResponse response) { + return true; + } + + /** + * @param centralAuthenticationService The centralAuthenticationService to + * set. + */ + public final void setCentralAuthenticationService(final CentralAuthenticationService centralAuthenticationService) { + this.centralAuthenticationService = centralAuthenticationService; + } + + + public final void setArgumentExtractor(final ArgumentExtractor argumentExtractor) { + this.argumentExtractor = argumentExtractor; + } + + /** + * @param validationSpecificationClass The authenticationSpecificationClass + * to set. + */ + public final void setValidationSpecificationClass(final Class validationSpecificationClass) { + this.validationSpecificationClass = validationSpecificationClass; + } + + /** + * @param failureView The failureView to set. + */ + public final void setFailureView(final String failureView) { + this.failureView = failureView; + } + + /** + * @param successView The successView to set. + */ + public final void setSuccessView(final String successView) { + this.successView = successView; + } + + /** + * @param proxyHandler The proxyHandler to set. + */ + public final void setProxyHandler(final ProxyHandler proxyHandler) { + this.proxyHandler = proxyHandler; + } + + /** + * Sets the services manager. + * + * @param servicesManager the new services manager + */ + public final void setServicesManager(final ServicesManager servicesManager) { + this.servicesManager = servicesManager; + } + + /** + * Ensure that the service is found and enabled in the service registry. + * @param registeredService the located entry in the registry + * @param service authenticating service + * @throws UnauthorizedServiceException + */ + private void verifyRegisteredServiceProperties(final RegisteredService registeredService, final Service service) { + if (registeredService == null) { + final String msg = String.format("ServiceManagement: Unauthorized Service Access. " + + "Service [%s] is not found in service registry.", service.getId()); + logger.warn(msg); + throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE, msg); + } + if (!registeredService.getAccessStrategy().isServiceAccessAllowed()) { + final String msg = String.format("ServiceManagement: Unauthorized Service Access. " + + "Service [%s] is not enabled in service registry.", service.getId()); + + logger.warn(msg); + throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE, msg); + } + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/AbstractLogoutAction.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/AbstractLogoutAction.java new file mode 100644 index 000000000000..68a5f33685ba --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/AbstractLogoutAction.java @@ -0,0 +1,105 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.web.support.WebUtils; +import org.springframework.webflow.action.AbstractAction; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Abstract logout action, which prevents caching on logout. + * + * @author Jerome Leleu + * @since 4.0.0 + */ +public abstract class AbstractLogoutAction extends AbstractAction { + + /** A constant for the logout index in web flow. */ + public static final String LOGOUT_INDEX = "logoutIndex"; + + /** The finish event in webflow. */ + public static final String FINISH_EVENT = "finish"; + + /** The front event in webflow. */ + public static final String FRONT_EVENT = "front"; + + /** The redirect to app event in webflow. */ + public static final String REDIRECT_APP_EVENT = "redirectApp"; + + @Override + protected final Event doExecute(final RequestContext context) throws Exception { + final HttpServletRequest request = WebUtils.getHttpServletRequest(context); + final HttpServletResponse response = WebUtils.getHttpServletResponse(context); + + preventCaching(response); + + return doInternalExecute(request, response, context); + } + + /** + * Execute the logout action after invalidating the cache. + * + * @param request the HTTP request. + * @param response the HTTP response. + * @param context the webflow context. + * @return the event triggered by this actions. + * @throws Exception exception returned by this action. + */ + protected abstract Event doInternalExecute(final HttpServletRequest request, final HttpServletResponse response, + final RequestContext context) throws Exception; + + /** + * Prevent caching by adding the appropriate headers. + * Copied from the preventCaching method in the + * {@link org.springframework.web.servlet.support.WebContentGenerator} class. + * + * @param response the HTTP response. + */ + protected final void preventCaching(final HttpServletResponse response) { + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 1L); + response.setHeader("Cache-Control", "no-cache"); + response.addHeader("Cache-Control", "no-store"); + } + + /** + * Put logout index into flow scope. + * + * @param context the context + * @param index the index + */ + protected final void putLogoutIndex(final RequestContext context, final int index) { + context.getFlowScope().put(LOGOUT_INDEX, index); + } + + /** + * Gets the logout index from the flow scope. + * + * @param context the context + * @return the logout index + */ + protected final int getLogoutIndex(final RequestContext context) { + final Object value = context.getFlowScope().get(LOGOUT_INDEX); + return value == null ? 0 : (Integer) value; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/AcceptableUsagePolicyFormAction.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/AcceptableUsagePolicyFormAction.java new file mode 100644 index 000000000000..a4ee55da654f --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/AcceptableUsagePolicyFormAction.java @@ -0,0 +1,97 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.web.flow; + +import org.jasig.cas.authentication.Credential; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.binding.message.MessageContext; +import org.springframework.webflow.action.EventFactorySupport; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Webflow action to receive and record the AUP response. + * @author Misagh Moayyed + * @since 4.1 + */ +public class AcceptableUsagePolicyFormAction { + + /** Event id to signal the policy needs to be accepted. **/ + protected static final String EVENT_ID_MUST_ACCEPT = "mustAccept"; + + /** Logger instance. **/ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final Map policyMap = new ConcurrentHashMap<>(); + + /** + * Verify whether the policy is accepted. + * + * @param context the context + * @param credential the credential + * @param messageContext the message context + * @return success if policy is accepted. {@link #EVENT_ID_MUST_ACCEPT} otherwise. + */ + public Event verify(final RequestContext context, final Credential credential, + final MessageContext messageContext) { + final String key = credential.getId(); + if (this.policyMap.containsKey(key)) { + final Boolean hasAcceptedPolicy = this.policyMap.get(key); + return hasAcceptedPolicy ? success() : accept(); + } + return accept(); + } + + /** + * Record the fact that the policy is accepted. + * + * @param context the context + * @param credential the credential + * @param messageContext the message context + * @return success if policy acceptance is recorded successfully. + */ + public Event submit(final RequestContext context, final Credential credential, + final MessageContext messageContext) { + this.policyMap.put(credential.getId(), Boolean.TRUE); + return success(); + } + + /** + * Success event. + * + * @return the event + */ + protected final Event success() { + return new EventFactorySupport().success(this); + } + + /** + * Accept event signaled by id {@link #EVENT_ID_MUST_ACCEPT}. + * + * @return the event + */ + protected final Event accept() { + return new EventFactorySupport().event(this, EVENT_ID_MUST_ACCEPT); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/AuthenticationViaFormAction.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/AuthenticationViaFormAction.java new file mode 100644 index 000000000000..17769a947231 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/AuthenticationViaFormAction.java @@ -0,0 +1,317 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.CasProtocolConstants; +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.MessageDescriptor; +import org.jasig.cas.authentication.AuthenticationException; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.HandlerResult; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.ticket.TicketException; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.TicketCreationException; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.jasig.cas.web.support.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.binding.message.MessageBuilder; +import org.springframework.binding.message.MessageContext; +import org.springframework.web.util.CookieGenerator; +import org.springframework.webflow.core.collection.LocalAttributeMap; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; +import java.util.Map; + +/** + * Action to authenticate credential and retrieve a TicketGrantingTicket for + * those credential. If there is a request for renew, then it also generates + * the Service Ticket required. + * + * @author Scott Battaglia + * @since 3.0.0.4 + */ +public class AuthenticationViaFormAction { + + /** Authentication success result. */ + public static final String SUCCESS = "success"; + + /** Authentication succeeded with warnings from authn subsystem that should be displayed to user. */ + public static final String SUCCESS_WITH_WARNINGS = "successWithWarnings"; + + /** Authentication success with "warn" enabled. */ + public static final String WARN = "warn"; + + /** Authentication failure result. */ + public static final String AUTHENTICATION_FAILURE = "authenticationFailure"; + + /** Error result. */ + public static final String ERROR = "error"; + + /** Flow scope attribute that determines if authn is happening at a public workstation. */ + public static final String PUBLIC_WORKSTATION_ATTRIBUTE = "publicWorkstation"; + + /** Logger instance. **/ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + /** Core we delegate to for handling all ticket related tasks. */ + @NotNull + private CentralAuthenticationService centralAuthenticationService; + + @NotNull + private CookieGenerator warnCookieGenerator; + + + /** + * Handle the submission of credentials from the post. + * + * @param context the context + * @param credential the credential + * @param messageContext the message context + * @return the event + * @since 4.1.0 + */ + public final Event submit(final RequestContext context, final Credential credential, + final MessageContext messageContext) { + if (!checkLoginTicketIfExists(context)) { + return returnInvalidLoginTicketEvent(context, messageContext); + } + + if (isRequestAskingForServiceTicket(context)) { + return grantServiceTicket(context, credential); + } + + return createTicketGrantingTicket(context, credential, messageContext); + } + + /** + * Tries to to determine if the login ticket in the request flow scope + * matches the login ticket provided by the request. The comparison + * is case-sensitive. + * + * @param context the context + * @return true if valid + * @since 4.1.0 + */ + protected boolean checkLoginTicketIfExists(final RequestContext context) { + final String loginTicketFromFlowScope = WebUtils.getLoginTicketFromFlowScope(context); + final String loginTicketFromRequest = WebUtils.getLoginTicketFromRequest(context); + + logger.trace("Comparing login ticket in the flow scope [{}] with login ticket in the request [{}]", + loginTicketFromFlowScope, loginTicketFromRequest); + return StringUtils.equals(loginTicketFromFlowScope, loginTicketFromRequest); + } + + /** + * Return invalid login ticket event. + * + * @param context the context + * @param messageContext the message context + * @return the error event + * @since 4.1.0 + */ + protected Event returnInvalidLoginTicketEvent(final RequestContext context, final MessageContext messageContext) { + final String loginTicketFromRequest = WebUtils.getLoginTicketFromRequest(context); + logger.warn("Invalid login ticket [{}]", loginTicketFromRequest); + messageContext.addMessage(new MessageBuilder().error().code("error.invalid.loginticket").build()); + return newEvent(ERROR); + } + + /** + * Is request asking for service ticket? + * + * @param context the context + * @return true, if both service and tgt are found, and the request is not asking to renew. + * @since 4.1.0 + */ + protected boolean isRequestAskingForServiceTicket(final RequestContext context) { + final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context); + final Service service = WebUtils.getService(context); + return (StringUtils.isNotBlank(context.getRequestParameters().get(CasProtocolConstants.PARAMETER_RENEW)) + && ticketGrantingTicketId != null + && service != null); + } + + /** + * Grant service ticket for the given credential based on the service and tgt + * that are found in the request context. + * + * @param context the context + * @param credential the credential + * @return the resulting event. Warning, authentication failure or error. + * @since 4.1.0 + */ + protected Event grantServiceTicket(final RequestContext context, final Credential credential) { + final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context); + try { + final Service service = WebUtils.getService(context); + final ServiceTicket serviceTicketId = this.centralAuthenticationService.grantServiceTicket( + ticketGrantingTicketId, service, credential); + WebUtils.putServiceTicketInRequestScope(context, serviceTicketId); + putWarnCookieIfRequestParameterPresent(context); + return newEvent(WARN); + } catch (final AuthenticationException e) { + return newEvent(AUTHENTICATION_FAILURE, e); + } catch (final TicketCreationException e) { + logger.warn( + "Invalid attempt to access service using renew=true with different credential. " + + "Ending SSO session."); + this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId); + } catch (final TicketException e) { + return newEvent(ERROR, e); + } + return newEvent(ERROR); + + } + /** + * Create ticket granting ticket for the given credentials. + * Adds all warnings into the message context. + * + * @param context the context + * @param credential the credential + * @param messageContext the message context + * @return the resulting event. + * @since 4.1.0 + */ + protected Event createTicketGrantingTicket(final RequestContext context, final Credential credential, + final MessageContext messageContext) { + try { + final TicketGrantingTicket tgt = this.centralAuthenticationService.createTicketGrantingTicket(credential); + WebUtils.putTicketGrantingTicketInScopes(context, tgt); + putWarnCookieIfRequestParameterPresent(context); + putPublicWorkstationToFlowIfRequestParameterPresent(context); + if (addWarningMessagesToMessageContextIfNeeded(tgt, messageContext)) { + return newEvent(SUCCESS_WITH_WARNINGS); + } + return newEvent(SUCCESS); + } catch (final AuthenticationException e) { + return newEvent(AUTHENTICATION_FAILURE, e); + } catch (final Exception e) { + return newEvent(ERROR, e); + } + } + + /** + * Add warning messages to message context if needed. + * + * @param tgtId the tgt id + * @param messageContext the message context + * @return true if warnings were found and added, false otherwise. + * @since 4.1.0 + */ + protected boolean addWarningMessagesToMessageContextIfNeeded(final TicketGrantingTicket tgtId, final MessageContext messageContext) { + boolean foundAndAddedWarnings = false; + for (final Map.Entry entry : tgtId.getAuthentication().getSuccesses().entrySet()) { + for (final MessageDescriptor message : entry.getValue().getWarnings()) { + addWarningToContext(messageContext, message); + foundAndAddedWarnings = true; + } + } + return foundAndAddedWarnings; + + } + /** + * Put warn cookie if request parameter present. + * + * @param context the context + */ + private void putWarnCookieIfRequestParameterPresent(final RequestContext context) { + final HttpServletResponse response = WebUtils.getHttpServletResponse(context); + + if (StringUtils.isNotBlank(context.getExternalContext().getRequestParameterMap().get("warn"))) { + this.warnCookieGenerator.addCookie(response, "true"); + } else { + this.warnCookieGenerator.removeCookie(response); + } + } + + /** + * Put public workstation into the flow if request parameter present. + * + * @param context the context + */ + private void putPublicWorkstationToFlowIfRequestParameterPresent(final RequestContext context) { + if (StringUtils.isNotBlank(context.getExternalContext() + .getRequestParameterMap().get(PUBLIC_WORKSTATION_ATTRIBUTE))) { + context.getFlowScope().put(PUBLIC_WORKSTATION_ATTRIBUTE, Boolean.TRUE); + } + } + + /** + * New event based on the given id. + * + * @param id the id + * @return the event + */ + private Event newEvent(final String id) { + return new Event(this, id); + } + + /** + * New event based on the id, which contains an error attribute referring to the exception occurred. + * + * @param id the id + * @param error the error + * @return the event + */ + private Event newEvent(final String id, final Exception error) { + return new Event(this, id, new LocalAttributeMap("error", error)); + } + + public final void setCentralAuthenticationService(final CentralAuthenticationService centralAuthenticationService) { + this.centralAuthenticationService = centralAuthenticationService; + } + + public final void setWarnCookieGenerator(final CookieGenerator warnCookieGenerator) { + this.warnCookieGenerator = warnCookieGenerator; + } + + /** + * Sets ticket registry. + * + * @param ticketRegistry the ticket registry. No longer needed as the core service layer + * returns the correct object type. Will be removed in future versions. + * @deprecated As of 4.1 + */ + @Deprecated + public void setTicketRegistry(final TicketRegistry ticketRegistry) { + logger.warn("setTicketRegistry() has no effect and will be removed in future CAS versions."); + } + + /** + * Adds a warning message to the message context. + * + * @param context Message context. + * @param warning Warning message. + */ + private void addWarningToContext(final MessageContext context, final MessageDescriptor warning) { + final MessageBuilder builder = new MessageBuilder() + .warning() + .code(warning.getCode()) + .defaultText(warning.getDefaultMessage()) + .args(warning.getParams()); + context.addMessage(builder.build()); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/CasDefaultFlowUrlHandler.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/CasDefaultFlowUrlHandler.java new file mode 100644 index 000000000000..de617b3f8a53 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/CasDefaultFlowUrlHandler.java @@ -0,0 +1,83 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.springframework.webflow.context.servlet.DefaultFlowUrlHandler; +import org.springframework.webflow.core.collection.AttributeMap; + +import javax.servlet.http.HttpServletRequest; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Provides special handling for parameters in requests made to the CAS login + * webflow. + * + * @author Scott Battaglia + * @since 3.4 + */ +public final class CasDefaultFlowUrlHandler extends DefaultFlowUrlHandler { + + /** + * Default flow execution key parameter name, {@value}. + * Same as that used by {@link DefaultFlowUrlHandler}. + **/ + public static final String DEFAULT_FLOW_EXECUTION_KEY_PARAMETER = "execution"; + + /** Flow execution parameter name. */ + private String flowExecutionKeyParameter = DEFAULT_FLOW_EXECUTION_KEY_PARAMETER; + + /** + * Sets the parameter name used to carry flow execution key in request. + * + * @param parameterName Request parameter name. + */ + public void setFlowExecutionKeyParameter(final String parameterName) { + this.flowExecutionKeyParameter = parameterName; + } + + /** + * Get the flow execution key. + * + * @param request the current HTTP servlet request. + * @return the flow execution key. + */ + public String getFlowExecutionKey(final HttpServletRequest request) { + return request.getParameter(flowExecutionKeyParameter); + } + + @Override + public String createFlowExecutionUrl(final String flowId, final String flowExecutionKey, final HttpServletRequest request) { + final StringBuilder builder = new StringBuilder(); + builder.append(request.getRequestURI()); + builder.append('?'); + @SuppressWarnings("unchecked") + final Map flowParams = new LinkedHashMap(request.getParameterMap()); + flowParams.put(this.flowExecutionKeyParameter, flowExecutionKey); + appendQueryParameters(builder, flowParams, getEncodingScheme(request)); + return builder.toString(); + } + + @Override + public String createFlowDefinitionUrl(final String flowId, final AttributeMap input, final HttpServletRequest request) { + return request.getRequestURI() + + (request.getQueryString() != null ? '?' + + request.getQueryString() : ""); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/CompositeFlowExecutionKeyConverter.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/CompositeFlowExecutionKeyConverter.java new file mode 100644 index 000000000000..d4245740bfb0 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/CompositeFlowExecutionKeyConverter.java @@ -0,0 +1,57 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.springframework.binding.convert.converters.Converter; +import org.springframework.webflow.execution.repository.support.CompositeFlowExecutionKey; + + +/** + * Special converter for the {@link CompositeFlowExecutionKey} to return as a String. + * + * @author Jerome Leleu + * @since 4.0.0 + */ +@SuppressWarnings("rawtypes") +public final class CompositeFlowExecutionKeyConverter implements Converter { + + /** + * {@inheritDoc} + */ + @Override + public Class getSourceClass() { + return CompositeFlowExecutionKey.class; + } + + /** + * {@inheritDoc} + */ + @Override + public Class getTargetClass() { + return String.class; + } + + /** + * {@inheritDoc} + */ + @Override + public Object convertSourceToTargetClass(final Object source, final Class targetClass) throws Exception { + return source.toString(); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/FrontChannelLogoutAction.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/FrontChannelLogoutAction.java new file mode 100644 index 000000000000..c8e62dfecddf --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/FrontChannelLogoutAction.java @@ -0,0 +1,110 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import java.net.URLEncoder; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; + +import org.jasig.cas.logout.LogoutManager; +import org.jasig.cas.logout.LogoutRequest; +import org.jasig.cas.logout.LogoutRequestStatus; +import org.jasig.cas.web.support.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +/** + * Logout action for front SLO : find the next eligible service and perform front logout. + * + * @author Jerome Leleu + * @since 4.0.0 + */ +public final class FrontChannelLogoutAction extends AbstractLogoutAction { + /** Defines the default logout parameter for requests. */ + public static final String DEFAULT_LOGOUT_PARAMETER = "SAMLRequest"; + + /** Defines the parameter name that is passed to the flow which contains the logout request. */ + public static final String DEFAULT_FLOW_ATTRIBUTE_LOGOUT_URL = "logoutUrl"; + + private static final Logger LOGGER = LoggerFactory.getLogger(FrontChannelLogoutAction.class); + + private String logoutRequestParameter = DEFAULT_LOGOUT_PARAMETER; + + @NotNull + private final LogoutManager logoutManager; + + /** + * Build from the logout manager. + * + * @param logoutManager a logout manager. + */ + public FrontChannelLogoutAction(final LogoutManager logoutManager) { + this.logoutManager = logoutManager; + } + + @Override + protected Event doInternalExecute(final HttpServletRequest request, final HttpServletResponse response, + final RequestContext context) throws Exception { + + final List logoutRequests = WebUtils.getLogoutRequests(context); + final Integer startIndex = getLogoutIndex(context); + if (logoutRequests != null) { + for (int i = startIndex; i < logoutRequests.size(); i++) { + final LogoutRequest logoutRequest = logoutRequests.get(i); + if (logoutRequest.getStatus() == LogoutRequestStatus.NOT_ATTEMPTED) { + // assume it has been successful + logoutRequest.setStatus(LogoutRequestStatus.SUCCESS); + + // save updated index + putLogoutIndex(context, i + 1); + + final String logoutUrl = logoutRequest.getLogoutUrl().toExternalForm(); + LOGGER.debug("Using logout url [{}] for front-channel logout requests", logoutUrl); + + final String logoutMessage = logoutManager.createFrontChannelLogoutMessage(logoutRequest); + LOGGER.debug("Front-channel logout message to send under [{}] is [{}]", + this.logoutRequestParameter, logoutMessage); + + // redirect to application with SAML logout message + final UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(logoutUrl); + builder.queryParam(this.logoutRequestParameter, URLEncoder.encode(logoutMessage, "UTF-8")); + + return result(REDIRECT_APP_EVENT, DEFAULT_FLOW_ATTRIBUTE_LOGOUT_URL, builder.build().toUriString()); + } + } + } + + // no new service with front-channel logout -> finish logout + return new Event(this, FINISH_EVENT); + } + + public LogoutManager getLogoutManager() { + return logoutManager; + } + + public void setLogoutRequestParameter(final String logoutRequestParameter) { + this.logoutRequestParameter = logoutRequestParameter; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/GatewayServicesManagementCheck.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/GatewayServicesManagementCheck.java new file mode 100644 index 000000000000..75952123c4a8 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/GatewayServicesManagementCheck.java @@ -0,0 +1,69 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.services.UnauthorizedServiceException; +import org.jasig.cas.web.support.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.webflow.action.AbstractAction; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +import javax.validation.constraints.NotNull; + +/** + * Performs an authorization check for the gateway request if there is no Ticket Granting Ticket. + * + * @author Scott Battaglia + * @since 3.4.5 + */ +public class GatewayServicesManagementCheck extends AbstractAction { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @NotNull + private final ServicesManager servicesManager; + + /** + * Initialize the component with an instance of the services manager. + * @param servicesManager the service registry instance. + */ + public GatewayServicesManagementCheck(final ServicesManager servicesManager) { + this.servicesManager = servicesManager; + } + + @Override + protected Event doExecute(final RequestContext context) throws Exception { + final Service service = WebUtils.getService(context); + + final boolean match = this.servicesManager.matchesExistingService(service); + + if (match) { + return success(); + } + + final String msg = String.format("ServiceManagement: Unauthorized Service Access. " + + "Service [%s] does not match entries in service registry.", service.getId()); + logger.warn(msg); + throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE, msg); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/GenerateLoginTicketAction.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/GenerateLoginTicketAction.java new file mode 100644 index 000000000000..47f265c547e4 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/GenerateLoginTicketAction.java @@ -0,0 +1,64 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import javax.validation.constraints.NotNull; + +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.jasig.cas.web.support.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.webflow.execution.RequestContext; + + +/** + * Generates the login ticket parameter as described in section 3.5 of the + * CAS protocol. + * + * @author Marvin S. Addison + * @since 3.4.9 + * + */ +public class GenerateLoginTicketAction { + /** 3.5.1 - Login tickets SHOULD begin with characters "LT-". */ + private static final String PREFIX = "LT"; + + /** Logger instance. */ + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @NotNull + private UniqueTicketIdGenerator ticketIdGenerator; + + /** + * Generate the login ticket. + * + * @param context the context + * @return "generated" + */ + public final String generate(final RequestContext context) { + final String loginTicket = this.ticketIdGenerator.getNewTicketId(PREFIX); + logger.debug("Generated login ticket {}", loginTicket); + WebUtils.putLoginTicket(context, loginTicket); + return "generated"; + } + + public void setTicketIdGenerator(final UniqueTicketIdGenerator generator) { + this.ticketIdGenerator = generator; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/GenerateServiceTicketAction.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/GenerateServiceTicketAction.java new file mode 100644 index 000000000000..f599017037ef --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/GenerateServiceTicketAction.java @@ -0,0 +1,90 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.AuthenticationException; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.ticket.TicketException; +import org.jasig.cas.ticket.InvalidTicketException; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.web.support.WebUtils; +import org.springframework.util.StringUtils; +import org.springframework.webflow.action.AbstractAction; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +import javax.validation.constraints.NotNull; + +/** + * Action to generate a service ticket for a given Ticket Granting Ticket and + * Service. + * + * @author Scott Battaglia + * @since 3.0.0.4 + */ +public final class GenerateServiceTicketAction extends AbstractAction { + + /** Instance of CentralAuthenticationService. */ + @NotNull + private CentralAuthenticationService centralAuthenticationService; + + @Override + protected Event doExecute(final RequestContext context) { + final Service service = WebUtils.getService(context); + final String ticketGrantingTicket = WebUtils.getTicketGrantingTicketId(context); + + try { + final Credential credential = WebUtils.getCredential(context); + + final ServiceTicket serviceTicketId = this.centralAuthenticationService + .grantServiceTicket(ticketGrantingTicket, service, credential); + WebUtils.putServiceTicketInRequestScope(context, serviceTicketId); + return success(); + } catch (final AuthenticationException e) { + logger.error("Could not verify credentials to grant service ticket", e); + } catch (final TicketException e) { + if (e instanceof InvalidTicketException) { + this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicket); + } + if (isGatewayPresent(context)) { + return result("gateway"); + } + } + + return error(); + } + + public void setCentralAuthenticationService( + final CentralAuthenticationService centralAuthenticationService) { + this.centralAuthenticationService = centralAuthenticationService; + } + + /** + * Checks if gateway is present in the request params. + * + * @param context the context + * @return true, if gateway present + */ + protected boolean isGatewayPresent(final RequestContext context) { + return StringUtils.hasText(context.getExternalContext() + .getRequestParameterMap().get("gateway")); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/GenericSuccessViewAction.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/GenericSuccessViewAction.java new file mode 100644 index 000000000000..76406efbfabf --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/GenericSuccessViewAction.java @@ -0,0 +1,68 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.principal.NullPrincipal; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.ticket.InvalidTicketException; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Action that should execute prior to rendering the generic-success login view. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public final class GenericSuccessViewAction { + /** Log instance for logging events, info, warnings, errors, etc. */ + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final CentralAuthenticationService centralAuthenticationService; + + /** + * Instantiates a new Generic success view action. + * + * @param centralAuthenticationService the central authentication service + */ + public GenericSuccessViewAction(final CentralAuthenticationService centralAuthenticationService) { + this.centralAuthenticationService = centralAuthenticationService; + } + + /** + * Gets authentication principal. + * + * @param ticketGrantingTicketId the ticket granting ticket id + * @return the authentication principal, or {@link org.jasig.cas.authentication.principal.NullPrincipal} + * if none was available. + */ + public Principal getAuthenticationPrincipal(final String ticketGrantingTicketId) { + try { + final TicketGrantingTicket ticketGrantingTicket = + this.centralAuthenticationService.getTicket(ticketGrantingTicketId, TicketGrantingTicket.class); + return ticketGrantingTicket.getAuthentication().getPrincipal(); + } catch (final InvalidTicketException e){ + logger.warn(e.getMessage()); + } + logger.debug("In the absence of valid TGT, the authentication principal cannot be determined. Returning {}", + NullPrincipal.class.getSimpleName()); + return NullPrincipal.getInstance(); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/InitialFlowSetupAction.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/InitialFlowSetupAction.java new file mode 100644 index 000000000000..f633c477025f --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/InitialFlowSetupAction.java @@ -0,0 +1,153 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.services.UnauthorizedServiceException; +import org.jasig.cas.web.support.ArgumentExtractor; +import org.jasig.cas.web.support.CookieRetrievingCookieGenerator; +import org.jasig.cas.web.support.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; +import org.springframework.webflow.action.AbstractAction; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; +import org.springframework.webflow.execution.repository.NoSuchFlowExecutionException; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.util.List; + +/** + * Class to automatically set the paths for the CookieGenerators. + *

+ * Note: This is technically not threadsafe, but because its overriding with a + * constant value it doesn't matter. + *

+ * Note: As of CAS 3.1, this is a required class that retrieves and exposes the + * values in the two cookies for subclasses to use. + * + * @author Scott Battaglia + * @since 3.1 + */ +public final class InitialFlowSetupAction extends AbstractAction { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** The services manager with access to the registry. **/ + @NotNull + private ServicesManager servicesManager; + + /** CookieGenerator for the Warnings. */ + @NotNull + private CookieRetrievingCookieGenerator warnCookieGenerator; + + /** CookieGenerator for the TicketGrantingTickets. */ + @NotNull + private CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator; + + /** Extractors for finding the service. */ + @NotNull + @Size(min=1) + private List argumentExtractors; + + /** Boolean to note whether we've set the values on the generators or not. */ + private boolean pathPopulated; + + /** If no authentication request from a service is present, halt and warn the user. */ + private boolean enableFlowOnAbsentServiceRequest = true; + + @Override + protected Event doExecute(final RequestContext context) throws Exception { + final HttpServletRequest request = WebUtils.getHttpServletRequest(context); + if (!this.pathPopulated) { + final String contextPath = context.getExternalContext().getContextPath(); + final String cookiePath = StringUtils.hasText(contextPath) ? contextPath + '/' : "/"; + logger.info("Setting path for cookies to: {} ", cookiePath); + this.warnCookieGenerator.setCookiePath(cookiePath); + this.ticketGrantingTicketCookieGenerator.setCookiePath(cookiePath); + this.pathPopulated = true; + } + + WebUtils.putTicketGrantingTicketInScopes(context, + this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request)); + + WebUtils.putWarningCookie(context, + Boolean.valueOf(this.warnCookieGenerator.retrieveCookieValue(request))); + + final Service service = WebUtils.getService(this.argumentExtractors, context); + + + if (service != null) { + logger.debug("Placing service in context scope: [{}]", service.getId()); + + final RegisteredService registeredService = this.servicesManager.findServiceBy(service); + if (registeredService != null && registeredService.getAccessStrategy().isServiceAccessAllowed()) { + logger.debug("Placing registered service [{}] with id [{}] in context scope", + registeredService.getServiceId(), + registeredService.getId()); + WebUtils.putRegisteredService(context, registeredService); + } + } else if (!this.enableFlowOnAbsentServiceRequest) { + logger.warn("No service authentication request is available at [{}]. CAS is configured to disable the flow.", + WebUtils.getHttpServletRequest(context).getRequestURL()); + throw new NoSuchFlowExecutionException(context.getFlowExecutionContext().getKey(), + new UnauthorizedServiceException("screen.service.required.message", "Service is required")); + } + WebUtils.putService(context, service); + return result("success"); + } + + public void setTicketGrantingTicketCookieGenerator( + final CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator) { + this.ticketGrantingTicketCookieGenerator = ticketGrantingTicketCookieGenerator; + } + + public void setWarnCookieGenerator(final CookieRetrievingCookieGenerator warnCookieGenerator) { + this.warnCookieGenerator = warnCookieGenerator; + } + + public void setArgumentExtractors(final List argumentExtractors) { + this.argumentExtractors = argumentExtractors; + } + + /** + * Set the service manager to allow access to the registry + * to retrieve the registered service details associated + * with an incoming service. + * Since 4.1 + * @param servicesManager the services manager + */ + public void setServicesManager(final ServicesManager servicesManager) { + this.servicesManager = servicesManager; + } + + /** + * Decide whether CAS should allow authentication requests + * when no service is present in the request. Default is enabled. + * + * @param enableFlowOnAbsentServiceRequest the enable flow on absent service request + */ + public void setEnableFlowOnAbsentServiceRequest(final boolean enableFlowOnAbsentServiceRequest) { + this.enableFlowOnAbsentServiceRequest = enableFlowOnAbsentServiceRequest; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/LogoutAction.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/LogoutAction.java new file mode 100644 index 000000000000..7fdd42576bc3 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/LogoutAction.java @@ -0,0 +1,101 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.logout.LogoutRequest; +import org.jasig.cas.logout.LogoutRequestStatus; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.web.support.WebUtils; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * Action to delete the TGT and the appropriate cookies. + * It also performs the back-channel SLO on the services accessed by the user during its browsing. + * After this back-channel SLO, a front-channel SLO can be started if some services require it. + * The final logout page or a redirection url is also computed in this action. + * + * @author Scott Battaglia + * @author Jerome Leleu + * @since 3.0.0 + */ +public final class LogoutAction extends AbstractLogoutAction { + + /** The services manager. */ + @NotNull + private ServicesManager servicesManager; + + /** + * Boolean to determine if we will redirect to any url provided in the + * service request parameter. + */ + private boolean followServiceRedirects; + + @Override + protected Event doInternalExecute(final HttpServletRequest request, final HttpServletResponse response, + final RequestContext context) throws Exception { + + boolean needFrontSlo = false; + putLogoutIndex(context, 0); + final List logoutRequests = WebUtils.getLogoutRequests(context); + if (logoutRequests != null) { + for (final LogoutRequest logoutRequest : logoutRequests) { + // if some logout request must still be attempted + if (logoutRequest.getStatus() == LogoutRequestStatus.NOT_ATTEMPTED) { + needFrontSlo = true; + break; + } + } + } + + final String service = request.getParameter("service"); + if (this.followServiceRedirects && service != null) { + final Service webAppService = new SimpleWebApplicationServiceImpl(service); + final RegisteredService rService = this.servicesManager.findServiceBy(webAppService); + + if (rService != null && rService.getAccessStrategy().isServiceAccessAllowed()) { + context.getFlowScope().put("logoutRedirectUrl", service); + } + } + + // there are some front services to logout, perform front SLO + if (needFrontSlo) { + return new Event(this, FRONT_EVENT); + } else { + // otherwise, finish the logout process + return new Event(this, FINISH_EVENT); + } + } + + public void setFollowServiceRedirects(final boolean followServiceRedirects) { + this.followServiceRedirects = followServiceRedirects; + } + + public void setServicesManager(final ServicesManager servicesManager) { + this.servicesManager = servicesManager; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/LogoutConversionService.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/LogoutConversionService.java new file mode 100644 index 000000000000..7b14afbc8c93 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/LogoutConversionService.java @@ -0,0 +1,38 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.springframework.binding.convert.service.DefaultConversionService; + +/** + * Special conversion service with a {@link CompositeFlowExecutionKeyConverter}. + * + * @author Jerome Leleu + * @since 4.0.0 + */ +public class LogoutConversionService extends DefaultConversionService { + + /** + * Build a new conversion service with a {@link CompositeFlowExecutionKeyConverter}. + */ + public LogoutConversionService() { + super(); + addConverter(new CompositeFlowExecutionKeyConverter()); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/SelectiveFlowHandlerAdapter.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/SelectiveFlowHandlerAdapter.java new file mode 100644 index 000000000000..027d7ea84259 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/SelectiveFlowHandlerAdapter.java @@ -0,0 +1,60 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.springframework.util.Assert; +import org.springframework.webflow.mvc.servlet.FlowHandler; +import org.springframework.webflow.mvc.servlet.FlowHandlerAdapter; + +import javax.validation.constraints.NotNull; +import java.util.Collections; +import java.util.Set; + +/** + * Selective extension of {@link FlowHandlerAdapter} that only supports {@link FlowHandler}s matching one or + * more flow IDs. + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class SelectiveFlowHandlerAdapter extends FlowHandlerAdapter { + + /** List of supported flow IDs. */ + @NotNull + private Set supportedFlowIds; + + public void setSupportedFlowIds(final Set flowIdSet) { + this.supportedFlowIds = flowIdSet; + } + + public void setSupportedFlowId(final String flowId) { + this.supportedFlowIds = Collections.singleton(flowId); + } + + @Override + public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + Assert.isTrue(!supportedFlowIds.isEmpty(), "Must specify at least one supported flow ID"); + } + + @Override + public boolean supports(final Object handler) { + return handler instanceof FlowHandler && supportedFlowIds.contains(((FlowHandler) handler).getFlowId()); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/SendTicketGrantingTicketAction.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/SendTicketGrantingTicketAction.java new file mode 100644 index 000000000000..b1ee8c69b505 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/SendTicketGrantingTicketAction.java @@ -0,0 +1,178 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.CasProtocolConstants; +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.web.support.CookieRetrievingCookieGenerator; +import org.jasig.cas.web.support.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.webflow.action.AbstractAction; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +import javax.validation.constraints.NotNull; + +/** + * Action that handles the TicketGrantingTicket creation and destruction. If the + * action is given a TicketGrantingTicket and one also already exists, the old + * one is destroyed and replaced with the new one. This action always returns + * "success". + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public final class SendTicketGrantingTicketAction extends AbstractAction { + private static final Logger LOGGER = LoggerFactory.getLogger(SendTicketGrantingTicketAction.class); + + private boolean createSsoSessionCookieOnRenewAuthentications = true; + + @NotNull + private CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator; + + /** Instance of CentralAuthenticationService. */ + @NotNull + private final CentralAuthenticationService centralAuthenticationService; + + @NotNull + private final ServicesManager servicesManager; + + /** + * Instantiates a new Send ticket granting ticket action. + * + * @param ticketGrantingTicketCookieGenerator the ticket granting ticket cookie generator + * @param centralAuthenticationService the central authentication service + * @param servicesManager the services manager + */ + public SendTicketGrantingTicketAction(final CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator, + final CentralAuthenticationService centralAuthenticationService, + final ServicesManager servicesManager) { + super(); + this.ticketGrantingTicketCookieGenerator = ticketGrantingTicketCookieGenerator; + this.centralAuthenticationService = centralAuthenticationService; + this.servicesManager = servicesManager; + } + + @Override + protected Event doExecute(final RequestContext context) { + final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context); + final String ticketGrantingTicketValueFromCookie = (String) context.getFlowScope().get("ticketGrantingTicketId"); + + if (ticketGrantingTicketId == null) { + return success(); + } + + if (isAuthenticatingAtPublicWorkstation(context)) { + LOGGER.info("Authentication is at a public workstation. " + + "SSO cookie will not be generated. Subsequent requests will be challenged for authentication."); + } else if (!this.createSsoSessionCookieOnRenewAuthentications && isAuthenticationRenewed(context)) { + LOGGER.info("Authentication session is renewed but CAS is not configured to create the SSO session. " + + "SSO cookie will not be generated. Subsequent requests will be challenged for authentication."); + } else { + this.ticketGrantingTicketCookieGenerator.addCookie(WebUtils.getHttpServletRequest(context), WebUtils + .getHttpServletResponse(context), ticketGrantingTicketId); + } + + if (ticketGrantingTicketValueFromCookie != null && !ticketGrantingTicketId.equals(ticketGrantingTicketValueFromCookie)) { + this.centralAuthenticationService + .destroyTicketGrantingTicket(ticketGrantingTicketValueFromCookie); + } + + return success(); + } + + /** + * @deprecated As of 4.1, use constructors instead. + * @param ticketGrantingTicketCookieGenerator the id generator + */ + @Deprecated + public void setTicketGrantingTicketCookieGenerator(final CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator) { + logger.warn("setTicketGrantingTicketCookieGenerator() is deprecated and has no effect. Use constructors instead."); + } + + /** + * @deprecated As of 4.1, use constructors instead. + * @param centralAuthenticationService cas instance + */ + @Deprecated + public void setCentralAuthenticationService( + final CentralAuthenticationService centralAuthenticationService) { + logger.warn("setCentralAuthenticationService() is deprecated and has no effect. Use constructors instead."); + } + + public void setCreateSsoSessionCookieOnRenewAuthentications(final boolean createSsoSessionCookieOnRenewAuthentications) { + this.createSsoSessionCookieOnRenewAuthentications = createSsoSessionCookieOnRenewAuthentications; + } + + /** + * @deprecated As of 4.1, use constructors instead. + * @param servicesManager the service manager + */ + @Deprecated + public void setServicesManager(final ServicesManager servicesManager) { + logger.warn("setServicesManager() is deprecated and has no effect. Use constructors instead."); + } + + /** + * Tries to determine if authentication was created as part of a "renew" event. + * Renewed authentications can occur if the service is not allowed to participate + * in SSO or if a "renew" request parameter is specified. + * + * @param ctx the request context + * @return true if renewed + */ + private boolean isAuthenticationRenewed(final RequestContext ctx) { + if (ctx.getRequestParameters().contains(CasProtocolConstants.PARAMETER_RENEW)) { + LOGGER.debug("[{}] is specified for the request. The authentication session will be considered renewed.", + CasProtocolConstants.PARAMETER_RENEW); + return true; + } + + final Service service = WebUtils.getService(ctx); + if (service != null) { + final RegisteredService registeredService = this.servicesManager.findServiceBy(service); + if (registeredService != null) { + final boolean isAllowedForSso = registeredService.getAccessStrategy().isServiceAccessAllowedForSso(); + LOGGER.debug("Located [{}] in registry. Service access to participate in SSO is set to [{}]", + registeredService.getServiceId(), isAllowedForSso); + return !isAllowedForSso; + } + } + + return false; + } + + /** + * Is authenticating at a public workstation? + * + * @param ctx the ctx + * @return true if the cookie value is present + */ + private boolean isAuthenticatingAtPublicWorkstation(final RequestContext ctx) { + if (ctx.getFlowScope().contains(AuthenticationViaFormAction.PUBLIC_WORKSTATION_ATTRIBUTE)) { + LOGGER.debug("Public workstation flag detected. SSO session will be considered renewed."); + return true; + } + return false; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/ServiceAuthorizationCheck.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/ServiceAuthorizationCheck.java new file mode 100644 index 000000000000..f3e344415317 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/ServiceAuthorizationCheck.java @@ -0,0 +1,87 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.services.UnauthorizedServiceException; +import org.jasig.cas.web.support.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.webflow.action.AbstractAction; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; +import javax.validation.constraints.NotNull; + +/** + * Performs a basic check if an authentication request for a provided service is authorized to proceed + * based on the registered services registry configuration (or lack thereof). + * + * @author Dmitriy Kopylenko + * @since 3.5.1 + **/ +public final class ServiceAuthorizationCheck extends AbstractAction { + + @NotNull + private final ServicesManager servicesManager; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * Initialize the component with an instance of the services manager. + * @param servicesManager the service registry instance. + */ + public ServiceAuthorizationCheck(final ServicesManager servicesManager) { + this.servicesManager = servicesManager; + } + + @Override + protected Event doExecute(final RequestContext context) throws Exception { + final Service service = WebUtils.getService(context); + //No service == plain /login request. Return success indicating transition to the login form + if (service == null) { + return success(); + } + + if (this.servicesManager.getAllServices().isEmpty()) { + final String msg = String.format("No service definitions are found in the service manager. " + + "Service [%s] will not be automatically authorized to request authentication.", service.getId()); + logger.warn(msg); + throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_EMPTY_SVC_MGMR); + } + final RegisteredService registeredService = this.servicesManager.findServiceBy(service); + + if (registeredService == null) { + final String msg = String.format("ServiceManagement: Unauthorized Service Access. " + + "Service [%s] is not found in service registry.", service.getId()); + logger.warn(msg); + throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE, msg); + } + if (!registeredService.getAccessStrategy().isServiceAccessAllowed()) { + final String msg = String.format("ServiceManagement: Unauthorized Service Access. " + + "Service [%s] is not enabled in service registry.", service.getId()); + + logger.warn(msg); + throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE, msg); + } + + return success(); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/TerminateSessionAction.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/TerminateSessionAction.java new file mode 100644 index 000000000000..1f20a7de83a6 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/TerminateSessionAction.java @@ -0,0 +1,93 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; + +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.web.support.CookieRetrievingCookieGenerator; +import org.jasig.cas.web.support.WebUtils; +import org.springframework.webflow.action.EventFactorySupport; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +/** + * Terminates the CAS SSO session by destroying all SSO state data (i.e. TGT, cookies). + * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class TerminateSessionAction { + + /** Webflow event helper component. */ + private final EventFactorySupport eventFactorySupport = new EventFactorySupport(); + + /** The CORE to which we delegate for all CAS functionality. */ + @NotNull + private final CentralAuthenticationService centralAuthenticationService; + + /** CookieGenerator for TGT Cookie. */ + @NotNull + private final CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator; + + /** CookieGenerator for Warn Cookie. */ + @NotNull + private final CookieRetrievingCookieGenerator warnCookieGenerator; + + /** + * Creates a new instance with the given parameters. + * @param cas Core business logic object. + * @param tgtCookieGenerator TGT cookie generator. + * @param warnCookieGenerator Warn cookie generator. + */ + public TerminateSessionAction( + final CentralAuthenticationService cas, + final CookieRetrievingCookieGenerator tgtCookieGenerator, + final CookieRetrievingCookieGenerator warnCookieGenerator) { + this.centralAuthenticationService = cas; + this.ticketGrantingTicketCookieGenerator = tgtCookieGenerator; + this.warnCookieGenerator = warnCookieGenerator; + } + + /** + * Terminates the CAS SSO session by destroying the TGT (if any) and removing cookies related to the SSO session. + * + * @param context Request context. + * + * @return "success" + */ + public Event terminate(final RequestContext context) { + // in login's webflow : we can get the value from context as it has already been stored + String tgtId = WebUtils.getTicketGrantingTicketId(context); + // for logout, we need to get the cookie's value + if (tgtId == null) { + final HttpServletRequest request = WebUtils.getHttpServletRequest(context); + tgtId = this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request); + } + if (tgtId != null) { + WebUtils.putLogoutRequests(context, this.centralAuthenticationService.destroyTicketGrantingTicket(tgtId)); + } + final HttpServletResponse response = WebUtils.getHttpServletResponse(context); + this.ticketGrantingTicketCookieGenerator.removeCookie(response); + this.warnCookieGenerator.removeCookie(response); + return this.eventFactorySupport.success(this); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/TicketGrantingTicketCheckAction.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/TicketGrantingTicketCheckAction.java new file mode 100644 index 000000000000..c88260ceec3e --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/TicketGrantingTicketCheckAction.java @@ -0,0 +1,103 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.ticket.TicketException; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.web.support.WebUtils; +import org.springframework.util.StringUtils; +import org.springframework.webflow.action.AbstractAction; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +import javax.validation.constraints.NotNull; + +/** + * Webflow action that checks whether the TGT in the request context is valid. There are three possible outcomes: + * + *

    + *
  1. {@link #NOT_EXISTS} - TGT not found in flow request context.
  2. + *
  3. {@link #INVALID} TGT has expired or is not found in ticket registry.
  4. + *
  5. {@link #VALID} - TGT found in ticket registry and has not expired.
  6. + *
+ * + * @author Marvin S. Addison + * @since 4.0.0 + */ +public class TicketGrantingTicketCheckAction extends AbstractAction { + + /** + * TGT does not exist event ID={@value}. + **/ + public static final String NOT_EXISTS = "notExists"; + + /** + * TGT invalid event ID={@value}. + **/ + public static final String INVALID = "invalid"; + + /** + * TGT valid event ID={@value}. + **/ + public static final String VALID = "valid"; + + /** + * The Central authentication service. + */ + @NotNull + private final CentralAuthenticationService centralAuthenticationService; + + + /** + * Creates a new instance with the given ticket registry. + * + * @param centralAuthenticationService the central authentication service + */ + public TicketGrantingTicketCheckAction(final CentralAuthenticationService centralAuthenticationService) { + this.centralAuthenticationService = centralAuthenticationService; + } + + /** + * Determines whether the TGT in the flow request context is valid. + * + * @param requestContext Flow request context. + * + * @throws Exception in case ticket cannot be retrieved from the service layer + * @return {@link #NOT_EXISTS}, {@link #INVALID}, or {@link #VALID}. + */ + @Override + protected Event doExecute(final RequestContext requestContext) throws Exception { + final String tgtId = WebUtils.getTicketGrantingTicketId(requestContext); + if (!StringUtils.hasText(tgtId)) { + return new Event(this, NOT_EXISTS); + } + + String eventId = INVALID; + try { + final Ticket ticket = this.centralAuthenticationService.getTicket(tgtId, Ticket.class); + if (ticket != null && !ticket.isExpired()) { + eventId = VALID; + } + } catch (final TicketException e) { + logger.trace("Could not retrieve ticket id {} from registry.", e); + } + return new Event(this, eventId); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/package.html b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/package.html new file mode 100644 index 000000000000..428f5e8fa72f --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/flow/package.html @@ -0,0 +1,30 @@ + + + +

This package defines the Webflow for the complete Login workflow (TGT +Check, Form Check, Warning Check, Service Redirect) as a set of Actions.

+

The workflow is defined using the Spring Webflow package, which has +documentation at +http://static.springframework.org/spring-webflow/docs/pr3/api/

+ + + diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/package.html b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/package.html new file mode 100644 index 000000000000..125541e607ac --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/package.html @@ -0,0 +1,60 @@ + + + + + In the Servlet API, the WEB-INF/web.xml deployment descriptor maps + various URL values to Java classes. In Spring, the web.xml maps all + URL program requests to a single generic handler. The mapping of + particular URL values to particular classes is then part of the Spring + configuration, specifically in the cas-servlet.xml file: +
<!-- Handler Mapping -->
+<bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
+ <property name="mappings">
+  <props>
+   <prop key="/login">loginController</prop>
+   <prop key="/logout">logoutController</prop>
+   <prop key="/serviceValidate">serviceValidateController</prop>
+   <prop key="/validate">legacyValidateController</prop>
+   <prop key="/proxy">proxyController</prop>
+   <prop key="/proxyValidate">proxyValidateController</prop>
+   <prop key="/CentralAuthenticationService">xFireCentralAuthenticationService</prop>
+ </props>
+</bean>
+

Each named service is then configured as a bean. For example, + the /login processing is defined as

+
<bean id="loginController" class="org.jasig.cas.web.LoginController" autowire="byType">
+<property name="loginTokens" ref="loginTokens" />
+<property name="centralAuthenticationService" ref="centralAuthenticationService" />
+</bean>
+

This package then supplies the classes that implement each of + the URL services that are defined as part of the HTTP CAS protocol. + Each such class is what Spring calls a Controller (from the MVC or + Model, View, Controller paradigm). Generically, Controllers extract + information from the HttpRequest object (Cookies, Headers, + Certificates, Query parameters, etc.). In CAS, each Controller then + calls the CAS layer to perform some operation involving tickets. The + successful return object or failure from CAS is then added to a Map + called the Model and is passed to a JSP page or Java class called + the View that writes back a response.

+ + + diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/report/HealthCheckController.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/report/HealthCheckController.java new file mode 100644 index 000000000000..6771d9e0569a --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/report/HealthCheckController.java @@ -0,0 +1,90 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.report; + +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; + +import org.jasig.cas.monitor.HealthCheckMonitor; +import org.jasig.cas.monitor.HealthStatus; +import org.jasig.cas.monitor.Status; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.ModelAndView; + + +/** + * Reports overall CAS health based on the observations of the configured {@link HealthCheckMonitor} instance. + * + * @author Marvin S. Addison + * @since 3.5 + */ +@Controller("healthCheckController") +@RequestMapping("/status") +public class HealthCheckController { + + /** Prefix for custom response headers with health check details. */ + private static final String HEADER_PREFIX = "X-CAS-"; + + @NotNull + @Autowired + private HealthCheckMonitor healthCheckMonitor; + + /** + * Handle request. + * + * @param request the request + * @param response the response + * @return the model and view + * @throws Exception the exception + */ + @RequestMapping(method = RequestMethod.GET) + protected ModelAndView handleRequestInternal( + final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + + final HealthStatus healthStatus = this.healthCheckMonitor.observe(); + final StringBuilder sb = new StringBuilder(); + sb.append("Health: ").append(healthStatus.getCode()); + String name; + Status status; + int i = 0; + for (final Map.Entry entry : healthStatus.getDetails().entrySet()) { + name = entry.getKey(); + status = entry.getValue(); + response.addHeader("X-CAS-" + name, String.format("%s;%s", status.getCode(), status.getDescription())); + + sb.append("\n\n\t").append(++i).append('.').append(name).append(": "); + sb.append(status.getCode()); + if (status.getDescription() != null) { + sb.append(" - ").append(status.getDescription()); + } + } + response.setStatus(healthStatus.getCode().value()); + response.setContentType("text/plain"); + response.getOutputStream().write(sb.toString().getBytes(response.getCharacterEncoding())); + + // Return null to signal MVC framework that we handled response directly + return null; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/report/InternalConfigStateController.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/report/InternalConfigStateController.java new file mode 100644 index 000000000000..2e0d8e91acd1 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/report/InternalConfigStateController.java @@ -0,0 +1,254 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.web.report; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.util.MinimalPrettyPrinter; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.FilterProvider; +import com.fasterxml.jackson.databind.ser.PropertyWriter; +import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; +import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; +import org.apache.commons.lang3.StringEscapeUtils; +import org.jasig.cas.util.AbstractJacksonBackedJsonSerializer; +import org.jasig.cas.util.JsonSerializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.BeanIsAbstractException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Controller that exposes the CAS internal state and beans + * as JSON. The report is available at /status/config. + * @author Misagh Moayyed + * @since 4.1 + */ +@Controller("internalConfigController") +@RequestMapping("/status/config") +public final class InternalConfigStateController { + + private static final Logger LOGGER = LoggerFactory.getLogger(InternalConfigStateController.class); + + private static final String VIEW_CONFIG = "monitoring/viewConfig"; + + private static final String[] INCLUDE_PACKAGES = new String[] {"org.jasig"}; + + @Autowired(required = true) + private ApplicationContext applicationContext; + + @Autowired(required = true) + @Qualifier("casProperties") + private Properties casProperties; + + /** + * Handle request. + * + * @param request the request + * @param response the response + * @return the model and view + * @throws Exception the exception + */ + @RequestMapping(method = RequestMethod.GET) + protected ModelAndView handleRequestInternal( + final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + + final Map list = getBeans(this.applicationContext); + LOGGER.debug("Found [{}] beans to report", list.size()); + + final JsonSerializer serializer = new BeanObjectJsonSerializer(); + final StringBuilder builder = new StringBuilder(); + builder.append('['); + + final Set> entries = list.entrySet(); + final Iterator> it = entries.iterator(); + + while (it.hasNext()) { + final Map.Entry entry = it.next(); + final Object obj = entry.getValue(); + + final StringWriter writer = new StringWriter(); + writer.append('{'); + writer.append('\"' + entry.getKey() + "\":"); + serializer.toJson(writer, obj); + writer.append('}'); + builder.append(writer); + + if (it.hasNext()) { + builder.append(','); + } + } + builder.append(']'); + final ModelAndView mv = new ModelAndView(VIEW_CONFIG); + final String jsonData = StringEscapeUtils.escapeJson(builder.toString()); + + mv.addObject("jsonData", jsonData); + mv.addObject("properties", casProperties.entrySet()); + return mv; + } + + private static final class BeanObjectJsonSerializer extends AbstractJacksonBackedJsonSerializer { + private static final long serialVersionUID = 691461175315322624L; + + /** + * Instantiates a new Bean object json serializer. + */ + public BeanObjectJsonSerializer() { + super(new MinimalPrettyPrinter()); + } + + @Override + protected ObjectMapper initializeObjectMapper() { + final ObjectMapper mapper = super.initializeObjectMapper(); + + final FilterProvider filters = new SimpleFilterProvider() + .addFilter("beanObjectFilter", new CasSimpleBeanObjectFilter()); + mapper.setFilters(filters); + + mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + mapper.configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, false); + mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false); + mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + mapper.addMixIn(Object.class, CasSimpleBeanObjectFilter.class); + mapper.disable(SerializationFeature.INDENT_OUTPUT); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + + return mapper; + } + + @Override + protected Class getTypeToSerialize() { + return Object.class; + } + + @JsonFilter("beanObjectFilter") + private static class CasSimpleBeanObjectFilter extends SimpleBeanPropertyFilter { + private static final Logger LOGGER = getLogger(CasSimpleBeanObjectFilter.class); + + private static final String[] EXCLUDE_FIELDS = new String[] {"logger"}; + + @Override + public void serializeAsField(final Object pojo, final JsonGenerator jgen, + final SerializerProvider provider, + final PropertyWriter writer) throws Exception { + try { + if (!canSerializeField(pojo, writer)) { + return; + } + super.serializeAsField(pojo, jgen, provider, writer); + } catch (final Exception e) { + LOGGER.debug(e.getMessage()); + } + } + + /** + * Can serialize field? + * + * @param pojo the pojo + * @param writer the writer + * @return the boolean + */ + private boolean canSerializeField(final Object pojo, final PropertyWriter writer) { + boolean foundPackage = false; + final String packageName = pojo.getClass().getPackage().getName(); + for (int i = 0; !foundPackage && i < INCLUDE_PACKAGES.length; i++) { + foundPackage = (packageName.startsWith(INCLUDE_PACKAGES[i])); + } + if (!foundPackage) { + LOGGER.trace("Package [{}] is ignored", packageName); + return false; + } + + + boolean foundField = true; + final String fieldName = writer.getFullName().getSimpleName(); + for (int i = 0; foundField && i < EXCLUDE_FIELDS.length; i++) { + foundField = !fieldName.equalsIgnoreCase(EXCLUDE_FIELDS[i]); + } + + if (!foundField) { + LOGGER.trace("Field [{}] is excluded", fieldName); + return false; + } + return true; + } + } + } + + /** + * Gets beans in the application context. + * + * @param ctx the ctx + * @return the beans + */ + private static Map getBeans(final ApplicationContext ctx) { + final String[] all = BeanFactoryUtils.beanNamesIncludingAncestors(ctx); + + final Map singletons = new HashMap<>(all.length); + for (final String name : all) { + try { + final Object object = ctx.getBean(name); + if (object != null) { + + boolean foundPackage = false; + final String packageName = object.getClass().getPackage().getName(); + for (int i = 0; !foundPackage && i < INCLUDE_PACKAGES.length; i++) { + foundPackage = (packageName.startsWith(INCLUDE_PACKAGES[i])); + } + if (foundPackage) { + singletons.put(name, object); + } + + } + } catch (final BeanIsAbstractException e){ + LOGGER.debug("Skipping abstract bean definition. {}", e.getMessage()); + } catch (final Throwable e){ + LOGGER.trace(e.getMessage(), e); + } + } + + return singletons; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/report/SingleSignOnSessionsReportController.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/report/SingleSignOnSessionsReportController.java new file mode 100644 index 000000000000..702784fafaa8 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/report/SingleSignOnSessionsReportController.java @@ -0,0 +1,161 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.web.report; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import org.apache.commons.collections4.Predicate; +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.ModelAndView; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * SSO Report web controller that produces JSON data for the view. + * + * @author Misagh Moayyed + * @author Dmitriy Kopylenko + * @since 4.1 + */ +@Controller("singleSignOnSessionsReportController") +@RequestMapping("/statistics/ssosessions") +public final class SingleSignOnSessionsReportController { + + private static final String VIEW_SSO_SESSIONS = "monitoring/viewSsoSessions"; + + /** + * The enum Sso session attribute keys. + */ + private enum SsoSessionAttributeKeys { + AUTHENTICATED_PRINCIPAL("authenticated_principal"), + AUTHENTICATION_DATE("authentication_date"), + TICKET_GRANTING_TICKET("ticket_granting_ticket"), + NUMBER_OF_USES("number_of_uses"); + + private String attributeKey; + + /** + * Instantiates a new Sso session attribute keys. + * + * @param attributeKey the attribute key + */ + private SsoSessionAttributeKeys(final String attributeKey) { + this.attributeKey = attributeKey; + } + + @Override + public String toString() { + return this.attributeKey; + } + } + + private static final String ROOT_REPORT_ACTIVE_SESSIONS_KEY = "activeSsoSessions"; + + private static final String ROOT_REPORT_NA_KEY = "notAvailable"; + + private static final Logger LOGGER = LoggerFactory.getLogger(SingleSignOnSessionsReportController.class); + + private final ObjectMapper jsonMapper = new ObjectMapper(); + + @Autowired + private CentralAuthenticationService centralAuthenticationService; + + @Value("${sso.sessions.include.tgt:false}") + private boolean includeTicketGrantingTicketId; + + /** + * Instantiates a new Single sign on sessions report resource. + */ + public SingleSignOnSessionsReportController() { + this.jsonMapper.disable(SerializationFeature.INDENT_OUTPUT); + this.jsonMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + } + + /** + * Gets sso sessions. + * + * @return the sso sessions + */ + private Collection> getSsoSessions() { + final List> activeSessions = new ArrayList>(); + + for(final Ticket ticket : getNonExpiredTicketGrantingTickets()) { + final TicketGrantingTicket tgt = (TicketGrantingTicket) ticket; + + final Map sso = new HashMap<>(SsoSessionAttributeKeys.values().length); + sso.put(SsoSessionAttributeKeys.AUTHENTICATED_PRINCIPAL.toString(), tgt.getAuthentication().getPrincipal().getId()); + sso.put(SsoSessionAttributeKeys.AUTHENTICATION_DATE.toString(), tgt.getAuthentication().getAuthenticationDate()); + sso.put(SsoSessionAttributeKeys.NUMBER_OF_USES.toString(), tgt.getCountOfUses()); + if (this.includeTicketGrantingTicketId) { + sso.put(SsoSessionAttributeKeys.TICKET_GRANTING_TICKET.toString(), tgt.getId()); + } + + activeSessions.add(Collections.unmodifiableMap(sso)); + } + return Collections.unmodifiableCollection(activeSessions); + } + + /** + * Gets non expired ticket granting tickets. + * + * @return the non expired ticket granting tickets + */ + private Collection getNonExpiredTicketGrantingTickets() { + return this.centralAuthenticationService.getTickets(new Predicate() { + @Override + public boolean evaluate(final Object ticket) { + if (ticket instanceof TicketGrantingTicket) { + return !((TicketGrantingTicket) ticket).isExpired(); + } + return false; + } + }); + } + + /** + * Show sso sessions. + * + * @return the model and view where json data will be rendered + * @throws Exception thrown during json processing + */ + @RequestMapping(method = RequestMethod.GET) + public ModelAndView showSsoSessions() throws Exception { + final Map sessionsMap = new HashMap(1); + sessionsMap.put(ROOT_REPORT_ACTIVE_SESSIONS_KEY, getSsoSessions()); + final String jsonRepresentation = this.jsonMapper.writeValueAsString(sessionsMap); + final ModelAndView modelAndView = new ModelAndView(VIEW_SSO_SESSIONS); + modelAndView.addObject(ROOT_REPORT_ACTIVE_SESSIONS_KEY, jsonRepresentation); + return modelAndView; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/report/StatisticsController.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/report/StatisticsController.java new file mode 100644 index 000000000000..f21406263460 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/report/StatisticsController.java @@ -0,0 +1,187 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.report; + +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.health.HealthCheckRegistry; +import com.codahale.metrics.servlets.HealthCheckServlet; +import com.codahale.metrics.servlets.MetricsServlet; +import org.apache.commons.collections4.functors.TruePredicate; +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.context.ServletContextAware; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedList; +import java.util.Queue; + +/** + * @author Scott Battaglia + * @since 3.3.5 + */ +@Controller("statisticsController") +@RequestMapping("/statistics") +public final class StatisticsController implements ServletContextAware { + + private static final int NUMBER_OF_MILLISECONDS_IN_A_DAY = 86400000; + + private static final int NUMBER_OF_MILLISECONDS_IN_AN_HOUR = 3600000; + + private static final int NUMBER_OF_MILLISECONDS_IN_A_MINUTE = 60000; + + private static final int NUMBER_OF_MILLISECONDS_IN_A_SECOND = 1000; + + private static final int NUMBER_OF_BYTES_IN_A_KILOBYTE = 1024; + + private static final String MONITORING_VIEW_STATISTICS = "monitoring/viewStatistics"; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final Date upTimeStartDate = new Date(); + + @Value("${host.name}") + private String casTicketSuffix; + + @Autowired + private CentralAuthenticationService centralAuthenticationService; + + @Autowired + @Qualifier("metrics") + private MetricRegistry metricsRegistry; + + @Autowired + @Qualifier("healthCheckMetrics") + private HealthCheckRegistry healthCheckRegistry; + + /** + * Handles the request. + * + * @param httpServletRequest the http servlet request + * @param httpServletResponse the http servlet response + * @return the model and view + * @throws Exception the exception + */ + @RequestMapping(method = RequestMethod.GET) + protected ModelAndView handleRequestInternal(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) + throws Exception { + final ModelAndView modelAndView = new ModelAndView(MONITORING_VIEW_STATISTICS); + modelAndView.addObject("startTime", this.upTimeStartDate); + final double difference = System.currentTimeMillis() - this.upTimeStartDate.getTime(); + + modelAndView.addObject("upTime", calculateUptime(difference, new LinkedList( + Arrays.asList(NUMBER_OF_MILLISECONDS_IN_A_DAY, NUMBER_OF_MILLISECONDS_IN_AN_HOUR, + NUMBER_OF_MILLISECONDS_IN_A_MINUTE, NUMBER_OF_MILLISECONDS_IN_A_SECOND, 1)), + new LinkedList(Arrays.asList("day", "hour", "minute", "second", "millisecond")))); + + modelAndView.addObject("totalMemory", convertToMegaBytes(Runtime.getRuntime().totalMemory())); + modelAndView.addObject("maxMemory", convertToMegaBytes(Runtime.getRuntime().maxMemory())); + modelAndView.addObject("freeMemory", convertToMegaBytes(Runtime.getRuntime().freeMemory())); + modelAndView.addObject("availableProcessors", Runtime.getRuntime().availableProcessors()); + modelAndView.addObject("serverHostName", httpServletRequest.getServerName()); + modelAndView.addObject("serverIpAddress", httpServletRequest.getLocalAddr()); + modelAndView.addObject("casTicketSuffix", this.casTicketSuffix); + + int unexpiredTgts = 0; + int unexpiredSts = 0; + int expiredTgts = 0; + int expiredSts = 0; + + try { + final Collection tickets = this.centralAuthenticationService.getTickets(TruePredicate.INSTANCE); + + for (final Ticket ticket : tickets) { + if (ticket instanceof ServiceTicket) { + if (ticket.isExpired()) { + expiredSts++; + } else { + unexpiredSts++; + } + } else { + if (ticket.isExpired()) { + expiredTgts++; + } else { + unexpiredTgts++; + } + } + } + } catch (final UnsupportedOperationException e) { + logger.trace("The ticket registry doesn't support this information."); + } + + modelAndView.addObject("unexpiredTgts", unexpiredTgts); + modelAndView.addObject("unexpiredSts", unexpiredSts); + modelAndView.addObject("expiredTgts", expiredTgts); + modelAndView.addObject("expiredSts", expiredSts); + modelAndView.addObject("pageTitle", modelAndView.getViewName()); + + return modelAndView; + } + + /** + * Convert to megabytes from bytes. + * @param bytes the total number of bytes + * @return value converted to MB + */ + private double convertToMegaBytes(final double bytes) { + return bytes / NUMBER_OF_BYTES_IN_A_KILOBYTE / NUMBER_OF_BYTES_IN_A_KILOBYTE; + } + /** + * Calculates the up time. + * + * @param difference the difference + * @param calculations the calculations + * @param labels the labels + * @return the uptime as a string. + */ + protected String calculateUptime(final double difference, final Queue calculations, final Queue labels) { + if (calculations.isEmpty()) { + return ""; + } + + final int value = calculations.remove(); + final double time = Math.floor(difference / value); + final double newDifference = difference - time * value; + final String currentLabel = labels.remove(); + final String label = time == 0 || time > 1 ? currentLabel + 's' : currentLabel; + + return Integer.toString((int) time) + ' ' + label + ' ' + calculateUptime(newDifference, calculations, labels); + } + + @Override + public void setServletContext(final ServletContext servletContext) { + servletContext.setAttribute(MetricsServlet.METRICS_REGISTRY, this.metricsRegistry); + servletContext.setAttribute(MetricsServlet.SHOW_SAMPLES, Boolean.TRUE); + servletContext.setAttribute(HealthCheckServlet.HEALTH_CHECK_REGISTRY, this.healthCheckRegistry); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapter.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapter.java new file mode 100644 index 000000000000..381e371c072f --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapter.java @@ -0,0 +1,96 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.support; + +import javax.servlet.http.HttpServletRequest; +import java.util.Date; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Implementation of a HandlerInterceptorAdapter that keeps track of a mapping + * of IP Addresses to number of failures to authenticate. + *

+ * Note, this class relies on an external method for decrementing the counts + * (i.e. a Quartz Job) and runs independent of the threshold of the parent. + * + * @author Scott Battaglia + * @since 3.0.0.5 + */ +public abstract class AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapter + extends AbstractThrottledSubmissionHandlerInterceptorAdapter { + + private static final double SUBMISSION_RATE_DIVIDEND = 1000.0; + private final ConcurrentMap ipMap = new ConcurrentHashMap<>(); + + @Override + protected final boolean exceedsThreshold(final HttpServletRequest request) { + final Date last = this.ipMap.get(constructKey(request)); + if (last == null) { + return false; + } + return submissionRate(new Date(), last) > getThresholdRate(); + } + + @Override + protected final void recordSubmissionFailure(final HttpServletRequest request) { + this.ipMap.put(constructKey(request), new Date()); + } + + /** + * Construct key to be used by the throttling agent to track requests. + * + * @param request the request + * @return the string + */ + protected abstract String constructKey(HttpServletRequest request); + + /** + * This class relies on an external configuration to clean it up. It ignores the threshold data in the parent class. + */ + public final void decrementCounts() { + final Set> keys = this.ipMap.entrySet(); + logger.debug("Decrementing counts for throttler. Starting key count: {}", keys.size()); + + final Date now = new Date(); + for (final Iterator> iter = keys.iterator(); iter.hasNext();) { + final Map.Entry entry = iter.next(); + if (submissionRate(now, entry.getValue()) < getThresholdRate()) { + logger.trace("Removing entry for key {}", entry.getKey()); + iter.remove(); + } + } + logger.debug("Done decrementing count for throttler."); + } + + /** + * Computes the instantaneous rate in between two given dates corresponding to two submissions. + * + * @param a First date. + * @param b Second date. + * + * @return Instantaneous submission rate in submissions/sec, e.g. a - b. + */ + private double submissionRate(final Date a, final Date b) { + return SUBMISSION_RATE_DIVIDEND / (a.getTime() - b.getTime()); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/AbstractThrottledSubmissionHandlerInterceptorAdapter.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/AbstractThrottledSubmissionHandlerInterceptorAdapter.java new file mode 100644 index 000000000000..f922986f543d --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/AbstractThrottledSubmissionHandlerInterceptorAdapter.java @@ -0,0 +1,178 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.support; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.http.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.webflow.execution.RequestContext; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; + +/** + * Abstract implementation of the handler that has all of the logic. Encapsulates the logic in case we get it wrong! + * + * @author Scott Battaglia + * @since 3.3.5 + */ +public abstract class AbstractThrottledSubmissionHandlerInterceptorAdapter extends HandlerInterceptorAdapter implements InitializingBean { + + private static final int DEFAULT_FAILURE_THRESHOLD = 100; + + private static final int DEFAULT_FAILURE_RANGE_IN_SECONDS = 60; + + private static final String DEFAULT_USERNAME_PARAMETER = "username"; + + private static final String SUCCESSFUL_AUTHENTICATION_EVENT = "success"; + + /** Logger object. **/ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + @Min(0) + private int failureThreshold = DEFAULT_FAILURE_THRESHOLD; + + @Min(0) + private int failureRangeInSeconds = DEFAULT_FAILURE_RANGE_IN_SECONDS; + + @NotNull + private String usernameParameter = DEFAULT_USERNAME_PARAMETER; + + private double thresholdRate; + + + @Override + public void afterPropertiesSet() throws Exception { + this.thresholdRate = (double) failureThreshold / (double) failureRangeInSeconds; + } + + + @Override + public final boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object o) throws Exception { + // we only care about post because that's the only instance where we can get anything useful besides IP address. + if (!"POST".equals(request.getMethod())) { + return true; + } + + if (exceedsThreshold(request)) { + recordThrottle(request); + request.setAttribute(WebUtils.CAS_ACCESS_DENIED_REASON, "screen.blocked.message"); + response.sendError(HttpStatus.SC_FORBIDDEN, + "Access Denied for user [" + request.getParameter(usernameParameter) + + "] from IP Address [" + request.getRemoteAddr() + ']'); + return false; + } + + return true; + } + + @Override + public final void postHandle(final HttpServletRequest request, final HttpServletResponse response, + final Object o, final ModelAndView modelAndView) throws Exception { + if (!"POST".equals(request.getMethod())) { + return; + } + + final RequestContext context = (RequestContext) request.getAttribute("flowRequestContext"); + + if (context == null || context.getCurrentEvent() == null) { + return; + } + + // User successfully authenticated + if (SUCCESSFUL_AUTHENTICATION_EVENT.equals(context.getCurrentEvent().getId())) { + return; + } + + // User submitted invalid credentials, so we update the invalid login count + recordSubmissionFailure(request); + } + + public final void setFailureThreshold(final int failureThreshold) { + this.failureThreshold = failureThreshold; + } + + public final void setFailureRangeInSeconds(final int failureRangeInSeconds) { + this.failureRangeInSeconds = failureRangeInSeconds; + } + + public final void setUsernameParameter(final String usernameParameter) { + this.usernameParameter = usernameParameter; + } + + protected double getThresholdRate() { + return this.thresholdRate; + } + + protected int getFailureThreshold() { + return this.failureThreshold; + } + + protected int getFailureRangeInSeconds() { + return this.failureRangeInSeconds; + } + + protected String getUsernameParameter() { + return this.usernameParameter; + } + + /** + * Record throttling event. + * + * @param request the request + */ + protected void recordThrottle(final HttpServletRequest request) { + logger.warn("Throttling submission from {}. More than {} failed login attempts within {} seconds. " + + "Authentication attempt exceeds the failure threshold {}", + request.getRemoteAddr(), this.failureThreshold, this.failureRangeInSeconds, + this.failureThreshold); + } + + /** + * Record submission failure. + * + * @param request the request + */ + protected abstract void recordSubmissionFailure(HttpServletRequest request); + + /** + * Determine whether threshold has been exceeded. + * + * @param request the request + * @return true, if successful + */ + protected abstract boolean exceedsThreshold(HttpServletRequest request); + + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("failureThreshold", this.failureThreshold) + .append("failureRangeInSeconds", this.failureRangeInSeconds) + .append("usernameParameter", this.usernameParameter) + .append("thresholdRate", this.thresholdRate) + .toString(); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/CookieRetrievingCookieGenerator.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/CookieRetrievingCookieGenerator.java new file mode 100644 index 000000000000..e1a4dbb1faae --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/CookieRetrievingCookieGenerator.java @@ -0,0 +1,119 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.support; + +import org.jasig.cas.authentication.RememberMeCredential; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.util.CookieGenerator; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.lang.reflect.Method; + +/** + * Extends CookieGenerator to allow you to retrieve a value from a request. + * The cookie is automatically marked as httpOnly, if the servlet container has support for it. + * + *

+ * Also has support for RememberMe Services + * + * @author Scott Battaglia + * @author Misagh Moayyed + * @since 3.1 + * + */ +public class CookieRetrievingCookieGenerator extends CookieGenerator { + + private static final int DEFAULT_REMEMBER_ME_MAX_AGE = 7889231; + + /** The maximum age the cookie should be remembered for. + * The default is three months ({@value} in seconds, according to Google) */ + private int rememberMeMaxAge = DEFAULT_REMEMBER_ME_MAX_AGE; + + /** Responsible for manging and verifying the cookie value. **/ + private final CookieValueManager casCookieValueManager; + + /** + * Instantiates a new cookie retrieving cookie generator + * with a default cipher of {@link org.jasig.cas.web.support.NoOpCookieValueManager}. + */ + public CookieRetrievingCookieGenerator() { + this(new NoOpCookieValueManager()); + } + + /** + * Instantiates a new Cookie retrieving cookie generator. + * @param casCookieValueManager the cookie manager + */ + public CookieRetrievingCookieGenerator(final CookieValueManager casCookieValueManager) { + super(); + final Method setHttpOnlyMethod = ReflectionUtils.findMethod(Cookie.class, "setHttpOnly", boolean.class); + if(setHttpOnlyMethod != null) { + super.setCookieHttpOnly(true); + } else { + logger.debug("Cookie cannot be marked as HttpOnly; container is not using servlet 3.0."); + } + this.casCookieValueManager = casCookieValueManager; + } + /** + * Adds the cookie, taking into account {@link RememberMeCredential#REQUEST_PARAMETER_REMEMBER_ME} + * in the request. + * + * @param request the request + * @param response the response + * @param cookieValue the cookie value + */ + public void addCookie(final HttpServletRequest request, final HttpServletResponse response, final String cookieValue) { + final String theCookieValue = this.casCookieValueManager.buildCookieValue(cookieValue, request); + + if (!StringUtils.hasText(request.getParameter(RememberMeCredential.REQUEST_PARAMETER_REMEMBER_ME))) { + super.addCookie(response, theCookieValue); + } else { + final Cookie cookie = createCookie(theCookieValue); + cookie.setMaxAge(this.rememberMeMaxAge); + if (isCookieSecure()) { + cookie.setSecure(true); + } + response.addCookie(cookie); + } + } + + /** + * Retrieve cookie value. + * + * @param request the request + * @return the cookie value + */ + public String retrieveCookieValue(final HttpServletRequest request) { + try { + final Cookie cookie = org.springframework.web.util.WebUtils.getCookie( + request, getCookieName()); + return cookie == null ? null : this.casCookieValueManager.obtainCookieValue(cookie, request); + } catch (final Exception e) { + logger.debug(e.getMessage(), e); + } + return null; + } + + public void setRememberMeMaxAge(final int maxAge) { + this.rememberMeMaxAge = maxAge; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/CookieValueManager.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/CookieValueManager.java new file mode 100644 index 000000000000..c0dd9f2ba107 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/CookieValueManager.java @@ -0,0 +1,54 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.web.support; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.validation.constraints.NotNull; + +/** + * The {@link org.jasig.cas.web.support.CookieValueManager} is responsible for + * managing all cookies and their value structure for CAS. Implementations + * may choose to encode and sign the cookie value and optionally perform + * additional checks to ensure the integrity of the cookie. + * + * @author Misagh Moayyed + * @since 4.1 + */ +public interface CookieValueManager { + + /** + * Build cookie value. + * + * @param givenCookieValue the given cookie value + * @param request the request + * @return the original cookie value + */ + String buildCookieValue(@NotNull String givenCookieValue, @NotNull HttpServletRequest request); + + /** + * Obtain cookie value. + * + * @param cookie the cookie + * @param request the request + * @return the cookie value or null + */ + String obtainCookieValue(@NotNull Cookie cookie, @NotNull HttpServletRequest request); +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/DefaultCasCookieValueManager.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/DefaultCasCookieValueManager.java new file mode 100644 index 000000000000..0cb70e3f9628 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/DefaultCasCookieValueManager.java @@ -0,0 +1,120 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.web.support; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.util.CipherExecutor; +import org.jasig.cas.util.NoOpCipherExecutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +/** + * The {@link DefaultCasCookieValueManager} is responsible for... + * + * @author Misagh Moayyed + * @since 4.1 + */ +public final class DefaultCasCookieValueManager implements CookieValueManager { + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultCasCookieValueManager.class); + private static final char COOKIE_FIELD_SEPARATOR = '@'; + private static final int COOKIE_FIELDS_LENGTH = 3; + + /** The cipher exec that is responsible for encryption and signing of the cookie. */ + private final CipherExecutor cipherExecutor; + + /** + * Instantiates a new Cas cookie value manager. + * Set the default cipher to do absolutely nothing. + */ + public DefaultCasCookieValueManager() { + this(new NoOpCipherExecutor()); + } + + /** + * Instantiates a new Cas cookie value manager. + * + * @param cipherExecutor the cipher executor + */ + public DefaultCasCookieValueManager(final CipherExecutor cipherExecutor) { + this.cipherExecutor = cipherExecutor; + LOGGER.debug("Using cipher [{} to encrypt and decode the cookie", + this.cipherExecutor.getClass()); + } + + @Override + public String buildCookieValue(final String givenCookieValue, final HttpServletRequest request) { + final StringBuilder builder = new StringBuilder(givenCookieValue); + + final String remoteAddr = request.getRemoteAddr(); + if (StringUtils.isBlank(remoteAddr)) { + throw new IllegalStateException("Request does not specify a remote address"); + } + builder.append(COOKIE_FIELD_SEPARATOR); + builder.append(remoteAddr); + + final String userAgent = request.getHeader("user-agent"); + if (StringUtils.isBlank(userAgent)) { + throw new IllegalStateException("Request does not specify a user-agent"); + } + builder.append(COOKIE_FIELD_SEPARATOR); + builder.append(userAgent); + + final String res = builder.toString(); + LOGGER.debug("Encoding cookie value [{}]", res); + return this.cipherExecutor.encode(res); + } + + @Override + public String obtainCookieValue(final Cookie cookie, final HttpServletRequest request) { + final String cookieValue = this.cipherExecutor.decode(cookie.getValue()); + LOGGER.debug("Decoded cookie value is [{}]", cookieValue); + if (StringUtils.isBlank(cookieValue)) { + LOGGER.debug("Retrieved decoded cookie value is blank. Failed to decode cookie [{}]", cookie.getName()); + return null; + } + + final String[] cookieParts = cookieValue.split(String.valueOf(COOKIE_FIELD_SEPARATOR)); + if (cookieParts.length != COOKIE_FIELDS_LENGTH) { + throw new IllegalStateException("Invalid cookie. Required fields are missing"); + } + final String value = cookieParts[0]; + final String remoteAddr = cookieParts[1]; + final String userAgent = cookieParts[2]; + + if (StringUtils.isBlank(value) || StringUtils.isBlank(remoteAddr) + || StringUtils.isBlank(userAgent)) { + throw new IllegalStateException("Invalid cookie. Required fields are empty"); + } + + if (!remoteAddr.equals(request.getRemoteAddr())) { + throw new IllegalStateException("Invalid cookie. Required remote address does not match " + + request.getRemoteAddr()); + } + + if (!userAgent.equals(request.getHeader("user-agent"))) { + throw new IllegalStateException("Invalid cookie. Required user-agent does not match " + + request.getHeader("user-agent")); + } + return value; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter.java new file mode 100644 index 000000000000..9e7a52e67cc8 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter.java @@ -0,0 +1,45 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.support; + +import org.jasig.inspektr.common.web.ClientInfoHolder; + +import javax.servlet.http.HttpServletRequest; + +/** + * Attempts to throttle by both IP Address and username. Protects against instances where there is a NAT, such as + * a local campus wireless network. + * + * @author Scott Battaglia + * @since 3.3.5 + */ +public final class InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter + extends AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapter { + + @Override + protected String constructKey(final HttpServletRequest request) { + final String username = request.getParameter(getUsernameParameter()); + + if (username == null) { + return request.getRemoteAddr(); + } + + return ClientInfoHolder.getClientInfo().getClientIpAddress() + ';' + username.toLowerCase(); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapter.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapter.java new file mode 100644 index 000000000000..9313a52abc5f --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapter.java @@ -0,0 +1,39 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.support; + +import org.jasig.inspektr.common.web.ClientInfoHolder; + +import javax.servlet.http.HttpServletRequest; + +/** + * Throttles access attempts for failed logins by IP Address. This stores the attempts in memory. + * This is not good for a clustered environment unless the intended behavior is that this blocking is per-machine. + * + * @author Scott Battaglia + * @since 3.3.5 + */ +public final class InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapter + extends AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapter { + + @Override + protected String constructKey(final HttpServletRequest request) { + return ClientInfoHolder.getClientInfo().getClientIpAddress(); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter.java new file mode 100644 index 000000000000..30bfddbc1e25 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter.java @@ -0,0 +1,153 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.support; + +import org.jasig.inspektr.audit.AuditActionContext; +import org.jasig.inspektr.audit.AuditPointRuntimeInfo; +import org.jasig.inspektr.audit.AuditTrailManager; +import org.jasig.inspektr.common.web.ClientInfo; +import org.jasig.inspektr.common.web.ClientInfoHolder; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; + +import javax.servlet.http.HttpServletRequest; +import javax.sql.DataSource; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.Calendar; +import java.util.List; + +/** + * Works in conjunction with the Inspektr Library to block attempts to dictionary attack users. + *

+ * Defines a new Inspektr Action "THROTTLED_LOGIN_ATTEMPT" which keeps track of failed login attempts that don't result + * in AUTHENTICATION_FAILED methods + *

+ * This relies on the default Inspektr table layout and username construction. The username construction can be overriden + * in a subclass. + * + * @author Scott Battaglia + * @since 3.3.5 + */ +public class InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter + extends AbstractThrottledSubmissionHandlerInterceptorAdapter { + + private static final String DEFAULT_APPLICATION_CODE = "CAS"; + + private static final String DEFAULT_AUTHN_FAILED_ACTION = "AUTHENTICATION_FAILED"; + + private static final String INSPEKTR_ACTION = "THROTTLED_LOGIN_ATTEMPT"; + private static final double NUMBER_OF_MILLISECONDS_IN_SECOND = 1000.0; + + private final AuditTrailManager auditTrailManager; + + private final JdbcTemplate jdbcTemplate; + + private String applicationCode = DEFAULT_APPLICATION_CODE; + + private String authenticationFailureCode = DEFAULT_AUTHN_FAILED_ACTION; + + /** + * Instantiates a new inspektr throttled submission by ip address and username handler interceptor adapter. + * + * @param auditTrailManager the audit trail manager + * @param dataSource the data source + */ + public InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter(final AuditTrailManager auditTrailManager, + final DataSource dataSource) { + this.auditTrailManager = auditTrailManager; + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + @Override + protected boolean exceedsThreshold(final HttpServletRequest request) { + final String query = "SELECT AUD_DATE FROM COM_AUDIT_TRAIL WHERE AUD_CLIENT_IP = ? AND AUD_USER = ? " + + "AND AUD_ACTION = ? AND APPLIC_CD = ? AND AUD_DATE >= ? ORDER BY AUD_DATE DESC"; + final String userToUse = constructUsername(request, getUsernameParameter()); + final Calendar cutoff = Calendar.getInstance(); + cutoff.add(Calendar.SECOND, -1 * getFailureRangeInSeconds()); + final List failures = this.jdbcTemplate.query( + query, + new Object[] {request.getRemoteAddr(), userToUse, this.authenticationFailureCode, this.applicationCode, cutoff.getTime()}, + new int[] {Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.TIMESTAMP}, + new RowMapper() { + @Override + public Timestamp mapRow(final ResultSet resultSet, final int i) throws SQLException { + return resultSet.getTimestamp(1); + } + }); + if (failures.size() < 2) { + return false; + } + // Compute rate in submissions/sec between last two authn failures and compare with threshold + return NUMBER_OF_MILLISECONDS_IN_SECOND / (failures.get(0).getTime() - failures.get(1).getTime()) > getThresholdRate(); + } + + @Override + protected void recordSubmissionFailure(final HttpServletRequest request) { + // No internal counters to update + } + + @Override + protected void recordThrottle(final HttpServletRequest request) { + super.recordThrottle(request); + final String userToUse = constructUsername(request, getUsernameParameter()); + final ClientInfo clientInfo = ClientInfoHolder.getClientInfo(); + final AuditPointRuntimeInfo auditPointRuntimeInfo = new AuditPointRuntimeInfo() { + private static final long serialVersionUID = 1L; + + @Override + public String asString() { + return String.format("%s.recordThrottle()", this.getClass().getName()); + } + }; + final AuditActionContext context = new AuditActionContext( + userToUse, + userToUse, + INSPEKTR_ACTION, + this.applicationCode, + new java.util.Date(), + clientInfo.getClientIpAddress(), + clientInfo.getServerIpAddress(), + auditPointRuntimeInfo); + this.auditTrailManager.record(context); + } + + public final void setApplicationCode(final String applicationCode) { + this.applicationCode = applicationCode; + } + + public final void setAuthenticationFailureCode(final String authenticationFailureCode) { + this.authenticationFailureCode = authenticationFailureCode; + } + + /** + * Construct username from the request. + * + * @param request the request + * @param usernameParameter the username parameter + * @return the string + */ + protected String constructUsername(final HttpServletRequest request, final String usernameParameter) { + final String username = request.getParameter(usernameParameter); + return "[username: " + (username != null ? username : "") + ']'; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/NoOpCookieValueManager.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/NoOpCookieValueManager.java new file mode 100644 index 000000000000..d4e86189627e --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/NoOpCookieValueManager.java @@ -0,0 +1,42 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.web.support; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +/** + * Default cookie value builder that simply returns the given cookie value + * and does not perform any additional checks. + * @author Misagh Moayyed + * @since 4.1 + */ +public final class NoOpCookieValueManager implements CookieValueManager { + + @Override + public String buildCookieValue(final String givenCookieValue, final HttpServletRequest request) { + return givenCookieValue; + } + + @Override + public String obtainCookieValue(final Cookie cookie, final HttpServletRequest request) { + return cookie.getValue(); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/package.html b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/package.html new file mode 100644 index 000000000000..ad653cf3ba7b --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/support/package.html @@ -0,0 +1,25 @@ + + + +

Classes related to assisting in management of the web tier.

+ + \ No newline at end of file diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/view/Cas10ResponseView.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/view/Cas10ResponseView.java new file mode 100644 index 000000000000..a6b19d20b23d --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/view/Cas10ResponseView.java @@ -0,0 +1,49 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.view; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +/** + * Custom View to Return the CAS 1.0 Protocol Response. Implemented as a view + * class rather than a JSP (like CAS 2.0 spec) because of the requirement of the + * line feeds to be "\n". + * + * @author Scott Battaglia + * @since 3.0.0 + */ +public final class Cas10ResponseView extends AbstractCasView { + + @Override + protected void renderMergedOutputModel(final Map model, + final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + + if (this.successResponse) { + response.getWriter().print( + "yes\n" + getPrimaryAuthenticationFrom(model).getPrincipal().getId() + '\n'); + } else { + response.getWriter().print("no\n\n"); + } + } + + +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/view/Cas20ResponseView.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/view/Cas20ResponseView.java new file mode 100644 index 000000000000..44fc0039c09b --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/view/Cas20ResponseView.java @@ -0,0 +1,52 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.view; + +import org.springframework.web.servlet.View; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +/** + * Renders and prepares CAS2 views. This view is responsible + * to simply just prep the base model, and delegates to + * a the real view to render the final output. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class Cas20ResponseView extends AbstractDelegatingCasView { + + /** + * Instantiates a new Abstract cas jstl view. + * + * @param view the view + */ + protected Cas20ResponseView(final View view) { + super(view); + } + + @Override + protected void prepareMergedOutputModel(final Map model, final HttpServletRequest request, + final HttpServletResponse response) throws Exception { + super.putIntoModel(model, CasViewConstants.MODEL_ATTRIBUTE_NAME_PRINCIPAL, getPrincipal(model)); + super.putIntoModel(model, CasViewConstants.MODEL_ATTRIBUTE_NAME_CHAINED_AUTHENTICATIONS, getChainedAuthentications(model)); + super.putIntoModel(model, CasViewConstants.MODEL_ATTRIBUTE_NAME_PRIMARY_AUTHENTICATION, getPrimaryAuthenticationFrom(model)); + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/view/Cas30ResponseView.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/view/Cas30ResponseView.java new file mode 100644 index 000000000000..e62f996b4ea6 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/view/Cas30ResponseView.java @@ -0,0 +1,173 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.view; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.CasProtocolConstants; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.authentication.support.CasAttributeEncoder; +import org.springframework.web.servlet.view.AbstractUrlBasedView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Renders and prepares CAS2 views. This view is responsible + * to simply just prep the base model, and delegates to + * a the real view to render the final output. + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class Cas30ResponseView extends Cas20ResponseView { + + /** The attribute encoder instance. */ + @NotNull + private CasAttributeEncoder casAttributeEncoder; + + /** The Services manager. */ + @NotNull + private ServicesManager servicesManager; + + /** + * Instantiates a new Abstract cas response view. + * + * @param view the view + */ + protected Cas30ResponseView(final AbstractUrlBasedView view) { + super(view); + } + + @Override + protected void prepareMergedOutputModel(final Map model, final HttpServletRequest request, + final HttpServletResponse response) throws Exception { + + super.prepareMergedOutputModel(model, request, response); + + final Service service = super.getServiceFrom(model); + final RegisteredService registeredService = this.servicesManager.findServiceBy(service); + + final Map attributes = new HashMap<>(getPrincipalAttributesAsMultiValuedAttributes(model)); + attributes.put(CasProtocolConstants.VALIDATION_CAS_MODEL_ATTRIBUTE_NAME_AUTHENTICATION_DATE, + Collections.singleton(getAuthenticationDate(model))); + attributes.put(CasProtocolConstants.VALIDATION_CAS_MODEL_ATTRIBUTE_NAME_FROM_NEW_LOGIN, + Collections.singleton(isAssertionBackedByNewLogin(model))); + attributes.put(CasProtocolConstants.VALIDATION_REMEMBER_ME_ATTRIBUTE_NAME, + Collections.singleton(isRememberMeAuthentication(model))); + + decideIfCredentialPasswordShouldBeReleasedAsAttribute(attributes, model, registeredService); + decideIfProxyGrantingTicketShouldBeReleasedAsAttribute(attributes, model, registeredService); + + super.putIntoModel(model, + CasProtocolConstants.VALIDATION_CAS_MODEL_ATTRIBUTE_NAME_ATTRIBUTES, + this.casAttributeEncoder.encodeAttributes(attributes, getServiceFrom(model))); + } + + /** + * Decide if credential password should be released as attribute. + * The credential must have been cached as an authentication attribute + * and the attribute release policy must be allowed to release the + * attribute. + * + * @param attributes the attributes + * @param model the model + * @param service the service + */ + protected void decideIfCredentialPasswordShouldBeReleasedAsAttribute(final Map attributes, + final Map model, + final RegisteredService service) { + decideAttributeReleaseBasedOnServiceAttributePolicy(attributes, + getAuthenticationAttribute(model, CasViewConstants.MODEL_ATTRIBUTE_NAME_PRINCIPAL_CREDENTIAL), + CasViewConstants.MODEL_ATTRIBUTE_NAME_PRINCIPAL_CREDENTIAL, + service, service.getAttributeReleasePolicy().isAuthorizedToReleaseCredentialPassword()); + } + + /** + * Decide if PGT should be released as attribute. + * The PGT must have been cached as an authentication attribute + * and the attribute release policy must be allowed to release the + * attribute. + * + * @param attributes the attributes + * @param model the model + * @param service the service + */ + protected void decideIfProxyGrantingTicketShouldBeReleasedAsAttribute(final Map attributes, + final Map model, + final RegisteredService service) { + decideAttributeReleaseBasedOnServiceAttributePolicy(attributes, + getProxyGrantingTicketId(model), + CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET, + service, service.getAttributeReleasePolicy().isAuthorizedToReleaseProxyGrantingTicket()); + } + + /** + * Decide attribute release based on service attribute policy. + * + * @param attributes the attributes + * @param attributeValue the attribute value + * @param attributeName the attribute name + * @param service the service + * @param doesAttributePolicyAllow does attribute policy allow release of this attribute? + */ + protected void decideAttributeReleaseBasedOnServiceAttributePolicy(final Map attributes, + final String attributeValue, + final String attributeName, + final RegisteredService service, + final boolean doesAttributePolicyAllow) { + if (StringUtils.isNotBlank(attributeValue)) { + logger.debug("Obtained [{}] as an authentication attribute", attributeName); + + if (doesAttributePolicyAllow) { + logger.debug("Obtained [{}] is passed to the CAS validation payload", attributeName); + attributes.put(attributeName, Collections.singleton(attributeValue)); + } else { + logger.debug("Attribute release policy for [{}] does not authorize the release of [{}]", + service.getServiceId(), attributeName); + } + } else { + logger.trace("[{}] is not available and will not be released to the validation response.", attributeName); + } + } + + /** + * Sets services manager. + * + * @param servicesManager the services manager + * @since 4.1 + */ + public void setServicesManager(@NotNull final ServicesManager servicesManager) { + this.servicesManager = servicesManager; + } + + /** + * Sets cas attribute encoder. + * + * @param casAttributeEncoder the cas attribute encoder + * @since 4.1 + */ + public void setCasAttributeEncoder(@NotNull final CasAttributeEncoder casAttributeEncoder) { + this.casAttributeEncoder = casAttributeEncoder; + } +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/view/CasReloadableMessageBundle.java b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/view/CasReloadableMessageBundle.java new file mode 100644 index 000000000000..b0b944e26a02 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/view/CasReloadableMessageBundle.java @@ -0,0 +1,91 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.view; + +import java.util.Locale; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; + +/** + * An extension of the {@link ReloadableResourceBundleMessageSource} whose sole concern + * is to print a WARN message in cases where a language key is not found in the active and + * default bundles. + * + *

Note: By default, if a key not found in a localized bundle, Spring will auto-fallback + * to the default bundle that is messages.properties. However, if the key is also + * not found in the default bundle, and {@link #setUseCodeAsDefaultMessage(boolean)} + * is set to true, then only the requested code itself will be used as the message to display. + * In this case, the class will issue a WARN message instructing the caller that the bundle + * needs further attention. If {@link #setUseCodeAsDefaultMessage(boolean)} is set to false, + * only then a null value will be returned, which subsequently causes an instance + * of {@link org.springframework.context.NoSuchMessageException} to be thrown. + * + * @author Misagh Moayyed + * @since 4.0.0 + */ +public class CasReloadableMessageBundle extends ReloadableResourceBundleMessageSource { + + private String[] basenames; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + protected String getDefaultMessage(final String code) { + final String messageToReturn = super.getDefaultMessage(code); + if (!StringUtils.isBlank(messageToReturn) && messageToReturn.equals(code)) { + logger.warn("The code [{}] cannot be found in the default language bundle and will " + + "be used as the message itself.", code); + } + return messageToReturn; + } + + @Override + protected String getMessageInternal(final String code, final Object[] args, final Locale locale) { + boolean foundCode = false; + + if (!locale.equals(Locale.ENGLISH)) { + for (int i = 0; !foundCode && i < this.basenames.length; i++) { + final String filename = this.basenames[i] + '_' + locale; + + logger.debug("Examining language bundle [{}] for the code [{}]", filename, code); + final PropertiesHolder holder = this.getProperties(filename); + foundCode = holder != null && holder.getProperties() != null + && holder.getProperty(code) != null; + } + + if (!foundCode) { + logger.debug("The code [{}] cannot be found in the language bundle for the locale [{}]", + code, locale); + } + } + return super.getMessageInternal(code, args, locale); + } + + @Override + public void setBasenames(final String... basenames) { + this.basenames = basenames; + super.setBasenames(basenames); + } + + + +} diff --git a/cas-server-webapp-support/src/main/java/org/jasig/cas/web/view/package.html b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/view/package.html new file mode 100644 index 000000000000..f62351a1d2d1 --- /dev/null +++ b/cas-server-webapp-support/src/main/java/org/jasig/cas/web/view/package.html @@ -0,0 +1,26 @@ + + + +

Package dealing with custom views such as writing directly to + the response output for a CAS 1 response.

+ + \ No newline at end of file diff --git a/cas-server-webapp-support/src/site/site.xml b/cas-server-webapp-support/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-webapp-support/src/site/site.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/services/web/RegisteredServiceThemeBasedViewResolverTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/services/web/RegisteredServiceThemeBasedViewResolverTests.java new file mode 100644 index 000000000000..3798fa87a803 --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/services/web/RegisteredServiceThemeBasedViewResolverTests.java @@ -0,0 +1,98 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services.web; + +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.services.DefaultServicesManagerImpl; +import org.jasig.cas.services.InMemoryServiceRegistryDaoImpl; +import org.jasig.cas.services.RegisteredServiceImpl; +import org.jasig.cas.services.ServicesManager; +import org.junit.Before; +import org.junit.Test; +import org.springframework.webflow.execution.RequestContextHolder; +import org.springframework.webflow.test.MockRequestContext; + +import static org.junit.Assert.*; + +/** + * + * @author John Gasper + * @since 4.1.0 + * + */ +public class RegisteredServiceThemeBasedViewResolverTests { + + private RegisteredServiceThemeBasedViewResolver registeredServiceThemeBasedViewResolver; + + private ServicesManager servicesManager; + + @Before + public void setUp() throws Exception { + this.servicesManager = new DefaultServicesManagerImpl(new InMemoryServiceRegistryDaoImpl()); + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setTheme("myTheme"); + r.setId(1000); + r.setName("Test Service"); + r.setServiceId("myServiceId"); + this.servicesManager.save(r); + + final RegisteredServiceImpl r2 = new RegisteredServiceImpl(); + r2.setTheme(null); + r2.setId(1001); + r2.setName("Test Service 2"); + r2.setServiceId("myDefaultId"); + this.servicesManager.save(r2); + + this.registeredServiceThemeBasedViewResolver = new RegisteredServiceThemeBasedViewResolver("defaultTheme", this.servicesManager); + } + + @Test + public void verifyGetServiceWithTheme() throws Exception { + final MockRequestContext requestContext = new MockRequestContext(); + RequestContextHolder.setRequestContext(requestContext); + + final WebApplicationService webApplicationService = new SimpleWebApplicationServiceImpl("myServiceId"); + requestContext.getFlowScope().put("service", webApplicationService); + + assertEquals("/WEB-INF/view/jsp/myTheme/ui/casLoginView", + this.registeredServiceThemeBasedViewResolver.buildView("casLoginView").getUrl()); + } + + @Test + public void verifyGetServiceWithDefault() throws Exception { + final MockRequestContext requestContext = new MockRequestContext(); + RequestContextHolder.setRequestContext(requestContext); + + final WebApplicationService webApplicationService = new SimpleWebApplicationServiceImpl("myDefaultId"); + requestContext.getFlowScope().put("service", webApplicationService); + + assertEquals("/WEB-INF/view/jsp/defaultTheme/ui/casLoginView", + this.registeredServiceThemeBasedViewResolver.buildView("casLoginView").getUrl()); + } + + @Test + public void verifyNoService() throws Exception { + final MockRequestContext requestContext = new MockRequestContext(); + RequestContextHolder.setRequestContext(requestContext); + + assertEquals("/WEB-INF/view/jsp/defaultTheme/ui/casLoginView", + this.registeredServiceThemeBasedViewResolver.buildView("casLoginView").getUrl()); + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/services/web/ServiceThemeResolverTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/services/web/ServiceThemeResolverTests.java new file mode 100644 index 000000000000..8a6c0c2d07d3 --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/services/web/ServiceThemeResolverTests.java @@ -0,0 +1,103 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.services.web; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.services.DefaultServicesManagerImpl; +import org.jasig.cas.services.InMemoryServiceRegistryDaoImpl; +import org.jasig.cas.services.RegisteredServiceImpl; +import org.jasig.cas.services.ServicesManager; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.webflow.core.collection.LocalAttributeMap; +import org.springframework.webflow.core.collection.MutableAttributeMap; +import org.springframework.webflow.execution.RequestContext; +import org.springframework.webflow.execution.RequestContextHolder; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * + * @author Scott Battaglia + * @since 3.1 + * + */ +public class ServiceThemeResolverTests { + + private ServiceThemeResolver serviceThemeResolver; + + private ServicesManager servicesManager; + + @Before + public void setUp() throws Exception { + this.servicesManager = new DefaultServicesManagerImpl(new InMemoryServiceRegistryDaoImpl()); + + this.serviceThemeResolver = new ServiceThemeResolver(); + this.serviceThemeResolver.setDefaultThemeName("test"); + this.serviceThemeResolver.setServicesManager(this.servicesManager); + final Map mobileBrowsers = new HashMap<>(); + mobileBrowsers.put("Mozilla", "theme"); + this.serviceThemeResolver.setMobileBrowsers(mobileBrowsers); + } + + @Test + public void verifyGetServiceThemeDoesNotExist() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setTheme("myTheme"); + r.setId(1000); + r.setName("Test Service"); + r.setServiceId("myServiceId"); + + this.servicesManager.save(r); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + final RequestContext ctx = mock(RequestContext.class); + final MutableAttributeMap scope = new LocalAttributeMap(); + scope.put("service", TestUtils.getService(r.getServiceId())); + when(ctx.getFlowScope()).thenReturn(scope); + RequestContextHolder.setRequestContext(ctx); + request.addHeader("User-Agent", "Mozilla"); + assertEquals("test", this.serviceThemeResolver.resolveThemeName(request)); + } + + @Test + public void verifyGetDefaultService() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("service", "myServiceId"); + request.addHeader("User-Agent", "Mozilla"); + assertEquals("test", this.serviceThemeResolver.resolveThemeName(request)); + } + + @Test + public void verifyGetDefaultServiceWithNoServicesManager() { + this.serviceThemeResolver.setServicesManager(null); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("service", "myServiceId"); + request.addHeader("User-Agent", "Mozilla"); + assertEquals("test", this.serviceThemeResolver.resolveThemeName(request)); + } + + + +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/util/AutowiringSchedulerFactoryBeanTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/util/AutowiringSchedulerFactoryBeanTests.java new file mode 100644 index 000000000000..16e090cab12b --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/util/AutowiringSchedulerFactoryBeanTests.java @@ -0,0 +1,57 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.util; + +import org.junit.Before; +import org.junit.Test; +import org.quartz.Scheduler; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import static org.junit.Assert.assertEquals; + +/** + * Test case for {@link org.jasig.cas.util.AutowiringSchedulerFactoryBean} class. + * + * @author Marvin S. Addison + * @since 3.3.3 + * + */ +public class AutowiringSchedulerFactoryBeanTests { + private ApplicationContext context; + + private Scheduler scheduler; + + + @Before + public void setUp() throws Exception { + context = new ClassPathXmlApplicationContext( + "core-context.xml", + "applicationContext.xml"); + + this.scheduler = (Scheduler) context.getBean("autowiringSchedulerFactoryBean"); + this.scheduler.start(); + + } + + @Test + public void verifyAfterPropertiesSet() throws Exception { + assertEquals(1, this.scheduler.getTriggerGroupNames().size()); + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/AbstractServiceValidateControllerTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/AbstractServiceValidateControllerTests.java new file mode 100644 index 000000000000..3431fa11a1ec --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/AbstractServiceValidateControllerTests.java @@ -0,0 +1,306 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web; + +import org.jasig.cas.AbstractCentralAuthenticationServiceTest; +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.mock.MockValidationSpecification; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.proxy.ProxyHandler; +import org.jasig.cas.ticket.proxy.support.Cas10ProxyHandler; +import org.jasig.cas.ticket.proxy.support.Cas20ProxyHandler; +import org.jasig.cas.util.http.SimpleHttpClientFactoryBean; +import org.jasig.cas.validation.Cas20ProtocolValidationSpecification; +import org.jasig.cas.web.support.CasArgumentExtractor; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; + +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public abstract class AbstractServiceValidateControllerTests extends AbstractCentralAuthenticationServiceTest { + + protected ServiceValidateController serviceValidateController; + + @Autowired + private ServicesManager servicesManager; + + @Before + public void onSetUp() throws Exception { + final StaticApplicationContext context = new StaticApplicationContext(); + context.refresh(); + this.serviceValidateController = new ServiceValidateController(); + this.serviceValidateController.setCentralAuthenticationService(getCentralAuthenticationService()); + final Cas20ProxyHandler proxyHandler = new Cas20ProxyHandler(); + proxyHandler.setHttpClient(new SimpleHttpClientFactoryBean().getObject()); + this.serviceValidateController.setProxyHandler(proxyHandler); + this.serviceValidateController.setApplicationContext(context); + this.serviceValidateController.setArgumentExtractor(new CasArgumentExtractor()); + this.serviceValidateController.setServicesManager(this.servicesManager); + } + + private HttpServletRequest getHttpServletRequest() throws Exception { + final TicketGrantingTicket tId = getCentralAuthenticationService() + .createTicketGrantingTicket(TestUtils.getCredentialsWithSameUsernameAndPassword()); + getCentralAuthenticationService().grantServiceTicket(tId.getId(), TestUtils.getService()); + final ServiceTicket sId2 = getCentralAuthenticationService().grantServiceTicket(tId.getId(), TestUtils.getService()); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", TestUtils.getService().getId()); + request.addParameter("ticket", sId2.getId()); + request.addParameter("renew", "true"); + + return request; + } + + @Test + public void verifyAfterPropertesSetTestEverything() throws Exception { + this.serviceValidateController.setValidationSpecificationClass(Cas20ProtocolValidationSpecification.class); + this.serviceValidateController.setProxyHandler(new Cas20ProxyHandler()); + } + + @Test + public void verifyEmptyParams() throws Exception { + assertNotNull(this.serviceValidateController.handleRequestInternal( + new MockHttpServletRequest(), new MockHttpServletResponse()).getModel().get("code")); + } + + @Test + public void verifyValidServiceTicket() throws Exception { + final TicketGrantingTicket tId = getCentralAuthenticationService() + .createTicketGrantingTicket(TestUtils.getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket sId = getCentralAuthenticationService().grantServiceTicket(tId.getId(), TestUtils.getService()); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", TestUtils.getService().getId()); + request.addParameter("ticket", sId.getId()); + + assertEquals(ServiceValidateController.DEFAULT_SERVICE_SUCCESS_VIEW_NAME, + this.serviceValidateController.handleRequestInternal(request, + new MockHttpServletResponse()).getViewName()); + } + + @Test + public void verifyValidServiceTicketInvalidSpec() throws Exception { + assertEquals(ServiceValidateController.DEFAULT_SERVICE_FAILURE_VIEW_NAME, + this.serviceValidateController.handleRequestInternal(getHttpServletRequest(), new MockHttpServletResponse()).getViewName()); + } + + @Test(expected=RuntimeException.class) + public void verifyValidServiceTicketRuntimeExceptionWithSpec() throws Exception { + this.serviceValidateController.setValidationSpecificationClass(MockValidationSpecification.class); + + assertEquals(ServiceValidateController.DEFAULT_SERVICE_FAILURE_VIEW_NAME, + this.serviceValidateController.handleRequestInternal(getHttpServletRequest(), new MockHttpServletResponse()).getViewName()); + fail(TestUtils.CONST_EXCEPTION_EXPECTED); + } + + @Test + public void verifyInvalidServiceTicket() throws Exception { + final TicketGrantingTicket tId = getCentralAuthenticationService() + .createTicketGrantingTicket(TestUtils.getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket sId = getCentralAuthenticationService().grantServiceTicket(tId.getId(), TestUtils.getService()); + + getCentralAuthenticationService().destroyTicketGrantingTicket(tId.getId()); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", TestUtils.getService().getId()); + request.addParameter("ticket", sId.getId()); + + assertEquals(ServiceValidateController.DEFAULT_SERVICE_FAILURE_VIEW_NAME, + this.serviceValidateController.handleRequestInternal(request, + new MockHttpServletResponse()).getViewName()); + } + + @Test + public void verifyValidServiceTicketWithValidPgtNoProxyHandling() throws Exception { + this.serviceValidateController.setProxyHandler(new Cas10ProxyHandler()); + final TicketGrantingTicket tId = getCentralAuthenticationService() + .createTicketGrantingTicket(TestUtils.getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket sId = getCentralAuthenticationService() + .grantServiceTicket(tId.getId(), TestUtils.getService()); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", TestUtils.getService().getId()); + request.addParameter("ticket", sId.getId()); + request.addParameter("pgtUrl", "https://www.github.com"); + + assertEquals(ServiceValidateController.DEFAULT_SERVICE_SUCCESS_VIEW_NAME, + this.serviceValidateController.handleRequestInternal(request, + new MockHttpServletResponse()).getViewName()); + } + + @Test + public void verifyValidServiceTicketWithSecurePgtUrl() throws Exception { + this.serviceValidateController.setProxyHandler(new Cas10ProxyHandler()); + final ModelAndView modelAndView = getModelAndViewUponServiceValidationWithSecurePgtUrl(); + assertEquals(ServiceValidateController.DEFAULT_SERVICE_SUCCESS_VIEW_NAME, modelAndView.getViewName()); + + } + + @Test + public void verifyValidServiceTicketWithInvalidPgt() throws Exception { + this.serviceValidateController.setProxyHandler(new Cas10ProxyHandler()); + final TicketGrantingTicket tId = getCentralAuthenticationService() + .createTicketGrantingTicket(TestUtils.getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket sId = getCentralAuthenticationService().grantServiceTicket(tId.getId(), TestUtils.getService()); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", TestUtils.getService().getId()); + request.addParameter("ticket", sId.getId()); + request.addParameter("pgtUrl", "duh"); + + final ModelAndView modelAndView = this.serviceValidateController.handleRequestInternal(request, new MockHttpServletResponse()); + assertEquals(ServiceValidateController.DEFAULT_SERVICE_SUCCESS_VIEW_NAME, modelAndView.getViewName()); + assertNull(modelAndView.getModel().get("pgtIou")); + } + + @Test + public void verifyValidServiceTicketWithValidPgtAndProxyHandling() throws Exception { + final TicketGrantingTicket tId = getCentralAuthenticationService() + .createTicketGrantingTicket(TestUtils.getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket sId = getCentralAuthenticationService().grantServiceTicket(tId.getId(), TestUtils.getService()); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", TestUtils.getService().getId()); + request.addParameter("ticket", sId.getId()); + request.addParameter("pgtUrl", "https://www.github.com"); + + final ModelAndView modelAndView = this.serviceValidateController.handleRequestInternal(request, new MockHttpServletResponse()); + assertEquals(ServiceValidateController.DEFAULT_SERVICE_SUCCESS_VIEW_NAME, modelAndView.getViewName()); + assertNotNull(modelAndView.getModel().get("pgtIou")); + } + + @Test + public void verifyValidServiceTicketWithValidPgtAndProxyHandlerFailing() throws Exception { + final TicketGrantingTicket tId = getCentralAuthenticationService() + .createTicketGrantingTicket(TestUtils.getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket sId = getCentralAuthenticationService().grantServiceTicket(tId.getId(), TestUtils.getService()); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", TestUtils.getService().getId()); + request.addParameter("ticket", sId.getId()); + request.addParameter("pgtUrl", "https://www.github.com"); + + this.serviceValidateController.setProxyHandler(new ProxyHandler() { + @Override + public String handle(final Credential credential, final TicketGrantingTicket proxyGrantingTicketId) { + return null; + } + + @Override + public boolean canHandle(final Credential credential) { + return true; + } + }); + + final ModelAndView modelAndView = this.serviceValidateController.handleRequestInternal(request, new MockHttpServletResponse()); + assertEquals(ServiceValidateController.DEFAULT_SERVICE_FAILURE_VIEW_NAME, modelAndView.getViewName()); + assertNull(modelAndView.getModel().get("pgtIou")); + } + + @Test + public void verifyValidServiceTicketWithDifferentEncodingAndIgnoringCase() throws Exception { + this.serviceValidateController.setProxyHandler(new Cas10ProxyHandler()); + final TicketGrantingTicket tId = getCentralAuthenticationService() + .createTicketGrantingTicket(TestUtils.getCredentialsWithSameUsernameAndPassword()); + + final String origSvc = "http://www.jasig.org?param=hello+world"; + final ServiceTicket sId = getCentralAuthenticationService() + .grantServiceTicket(tId.getId(), TestUtils.getService(origSvc)); + + final String reqSvc = "http://WWW.JASIG.ORG?PARAM=hello%20world"; + + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", TestUtils.getService(reqSvc).getId()); + request.addParameter("ticket", sId.getId()); + + assertEquals(ServiceValidateController.DEFAULT_SERVICE_SUCCESS_VIEW_NAME, + this.serviceValidateController.handleRequestInternal(request, + new MockHttpServletResponse()).getViewName()); + } + + @Test + public void verifyValidServiceTicketWithDifferentEncoding() throws Exception { + this.serviceValidateController.setProxyHandler(new Cas10ProxyHandler()); + final TicketGrantingTicket tId = getCentralAuthenticationService() + .createTicketGrantingTicket(TestUtils.getCredentialsWithSameUsernameAndPassword()); + + final String origSvc = "http://www.jasig.org?param=hello+world"; + final ServiceTicket sId = getCentralAuthenticationService() + .grantServiceTicket(tId.getId(), TestUtils.getService(origSvc)); + + final String reqSvc = "http://www.jasig.org?param=hello%20world"; + + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", TestUtils.getService(reqSvc).getId()); + request.addParameter("ticket", sId.getId()); + + assertEquals(ServiceValidateController.DEFAULT_SERVICE_SUCCESS_VIEW_NAME, + this.serviceValidateController.handleRequestInternal(request, + new MockHttpServletResponse()).getViewName()); + } + + @Test + public void verifyValidServiceTicketAndPgtUrlMismatch() throws Exception { + final TicketGrantingTicket tId = getCentralAuthenticationService() + .createTicketGrantingTicket(TestUtils.getCredentialsWithSameUsernameAndPassword()); + + final Service svc = TestUtils.getService("proxyService"); + final ServiceTicket sId = getCentralAuthenticationService().grantServiceTicket(tId.getId(), svc); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", svc.getId()); + request.addParameter("ticket", sId.getId()); + request.addParameter("pgtUrl", "http://www.github.com"); + + final ModelAndView modelAndView = this.serviceValidateController.handleRequestInternal(request, new MockHttpServletResponse()); + assertEquals(ServiceValidateController.DEFAULT_SERVICE_FAILURE_VIEW_NAME, modelAndView.getViewName()); + assertNull(modelAndView.getModel().get("pgtIou")); + } + + protected final ModelAndView getModelAndViewUponServiceValidationWithSecurePgtUrl() throws Exception { + final TicketGrantingTicket tId = getCentralAuthenticationService() + .createTicketGrantingTicket(TestUtils.getCredentialsWithSameUsernameAndPassword()); + final ServiceTicket sId = getCentralAuthenticationService().grantServiceTicket(tId.getId(), TestUtils.getService()); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", TestUtils.getService().getId()); + request.addParameter("ticket", sId.getId()); + request.addParameter("pgtUrl", "https://www.github.com"); + + + return this.serviceValidateController + .handleRequestInternal(request, new MockHttpServletResponse()); + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/FlowExecutionExceptionResolverTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/FlowExecutionExceptionResolverTests.java new file mode 100644 index 000000000000..c832b5e417ca --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/FlowExecutionExceptionResolverTests.java @@ -0,0 +1,120 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web; + +import static org.junit.Assert.*; + + +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.view.RedirectView; +import org.springframework.webflow.execution.FlowExecutionKey; +import org.springframework.webflow.execution.repository.BadlyFormattedFlowExecutionKeyException; +import org.springframework.webflow.execution.repository.NoSuchFlowExecutionException; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class FlowExecutionExceptionResolverTests { + + private FlowExecutionExceptionResolver resolver; + + @Before + public void setUp() throws Exception { + this.resolver = new FlowExecutionExceptionResolver(); + } + + @Test + public void verifyNullPointerException() { + assertNull(this.resolver.resolveException(new MockHttpServletRequest(), + new MockHttpServletResponse(), null, new NullPointerException())); + } + + @Test + public void verifyNoSuchFlowExecutionException() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("test"); + ModelAndView model = this.resolver.resolveException(request, + new MockHttpServletResponse(), null, + new NoSuchFlowExecutionException(new FlowExecutionKey(){ + + private static final long serialVersionUID = 1443616250214416520L; + + @Override + public String toString() { + return "test"; + } + + @Override + public boolean equals(final Object o) { + return true; + } + + @Override + public int hashCode() { + return 0; + } + }, new RuntimeException())); + + assertEquals(request.getRequestURI(), ((RedirectView) model.getView()) + .getUrl()); + } + + @Test + public void verifyBadlyFormattedExecutionException() { + assertNull(this.resolver.resolveException(new MockHttpServletRequest(), + new MockHttpServletResponse(), null, + new BadlyFormattedFlowExecutionKeyException("invalidKey", "e2s1"))); + } + + @Test + public void verifyNoSuchFlowExecutionExeptionWithQueryString() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("test"); + request.setQueryString("test=test"); + ModelAndView model = this.resolver.resolveException(request, + new MockHttpServletResponse(), null, + new NoSuchFlowExecutionException(new FlowExecutionKey(){ + + private static final long serialVersionUID = -4750073902540974152L; + + @Override + public String toString() { + return "test"; + } + + @Override + public boolean equals(final Object o) { + return true; + } + + @Override + public int hashCode() { + return 0; + } + }, new RuntimeException())); + + assertEquals(request.getRequestURI() + "?" + request.getQueryString(), ((RedirectView) model.getView()) + .getUrl()); + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/ProxyControllerTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/ProxyControllerTests.java new file mode 100644 index 000000000000..795ef2657f71 --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/ProxyControllerTests.java @@ -0,0 +1,102 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web; + +import java.util.Map; + +import org.jasig.cas.AbstractCentralAuthenticationServiceTest; +import org.jasig.cas.TestUtils; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class ProxyControllerTests extends AbstractCentralAuthenticationServiceTest { + + private ProxyController proxyController; + + @Before + public void onSetUp() throws Exception { + this.proxyController = new ProxyController(); + this.proxyController + .setCentralAuthenticationService(getCentralAuthenticationService()); + + final StaticApplicationContext context = new StaticApplicationContext(); + context.refresh(); + this.proxyController.setApplicationContext(context); + } + + @Test + public void verifyNoParams() throws Exception { + assertEquals("INVALID_REQUEST", this.proxyController + .handleRequestInternal(new MockHttpServletRequest(), + new MockHttpServletResponse()).getModel() + .get("code")); + } + + @Test + public void verifyNonExistentPGT() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("pgt", "TestService"); + request.addParameter("targetService", "testDefault"); + + assertTrue(this.proxyController.handleRequestInternal(request, + new MockHttpServletResponse()).getModel().containsKey( + "code")); + } + + @Test + public void verifyExistingPGT() throws Exception { + final TicketGrantingTicket ticket = new TicketGrantingTicketImpl( + "ticketGrantingTicketId", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy()); + getTicketRegistry().addTicket(ticket); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request + .addParameter("pgt", ticket.getId()); + request.addParameter( + "targetService", "testDefault"); + + assertTrue(this.proxyController.handleRequestInternal(request, + new MockHttpServletResponse()).getModel().containsKey( + "ticket")); + } + + @Test + public void verifyNotAuthorizedPGT() throws Exception { + final TicketGrantingTicket ticket = new TicketGrantingTicketImpl("ticketGrantingTicketId", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy()); + getTicketRegistry().addTicket(ticket); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("pgt", ticket.getId()); + request.addParameter("targetService", "service"); + + final Map map = this.proxyController.handleRequestInternal(request, new MockHttpServletResponse()).getModel(); + assertTrue(!map.containsKey("ticket")); + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/AuthenticationExceptionHandlerTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/AuthenticationExceptionHandlerTests.java new file mode 100644 index 000000000000..622ef284ce74 --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/AuthenticationExceptionHandlerTests.java @@ -0,0 +1,64 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.authentication.AuthenticationException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.springframework.binding.message.MessageContext; + +import javax.security.auth.login.AccountNotFoundException; +import java.security.GeneralSecurityException; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +/** + * @author Marvin S. Addison + * @since 4.0.0 + */ +@RunWith(JUnit4.class) +public class AuthenticationExceptionHandlerTests { + + @Test + public void handleAccountNotFoundExceptionByDefefault() { + final AuthenticationExceptionHandler handler = new AuthenticationExceptionHandler(); + final MessageContext ctx = mock(MessageContext.class); + + final Map> map = new HashMap<>(); + map.put("notFound", AccountNotFoundException.class); + final String id = handler.handle(new AuthenticationException(map), ctx); + assertEquals(id, AccountNotFoundException.class.getSimpleName()); + } + + @Test + public void handleUnknownExceptionByDefefault() { + final AuthenticationExceptionHandler handler = new AuthenticationExceptionHandler(); + final MessageContext ctx = mock(MessageContext.class); + + final Map> map = new HashMap<>(); + map.put("unknown", GeneralSecurityException.class); + final String id = handler.handle(new AuthenticationException(map), ctx); + assertEquals(id, "UNKNOWN"); + } + +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/AuthenticationViaFormActionTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/AuthenticationViaFormActionTests.java new file mode 100644 index 000000000000..77b0978a41ae --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/AuthenticationViaFormActionTests.java @@ -0,0 +1,238 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.AbstractCentralAuthenticationServiceTest; +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Credential; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.web.support.WebUtils; +import org.junit.Before; +import org.junit.Test; +import org.springframework.binding.message.MessageContext; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.validation.BindException; +import org.springframework.web.util.CookieGenerator; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.execution.RequestContext; +import org.springframework.webflow.test.MockRequestContext; + +import javax.validation.constraints.NotNull; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Scott Battaglia + * @since 3.0.0.4 + */ +public class AuthenticationViaFormActionTests extends AbstractCentralAuthenticationServiceTest { + + private AuthenticationViaFormAction action; + + private CookieGenerator warnCookieGenerator; + + @Before + public void onSetUp() throws Exception { + this.action = new AuthenticationViaFormAction(); + + this.warnCookieGenerator = new CookieGenerator(); + this.warnCookieGenerator.setCookieName("WARN"); + this.warnCookieGenerator.setCookieName("TGT"); + this.warnCookieGenerator.setCookieDomain("/"); + this.warnCookieGenerator.setCookiePath("/"); + + this.action.setCentralAuthenticationService(getCentralAuthenticationService()); + this.action.setWarnCookieGenerator(this.warnCookieGenerator); + } + + @Test + public void verifySuccessfulAuthenticationWithNoService() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockRequestContext context = new MockRequestContext(); + + WebUtils.putLoginTicket(context, "LOGIN"); + request.addParameter("lt", "LOGIN"); + request.addParameter("username", "test"); + request.addParameter("password", "test"); + + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + final Credential c = TestUtils.getCredentialsWithSameUsernameAndPassword(); + putCredentialInRequestScope(context, c); + + final MessageContext messageContext = mock(MessageContext.class); + assertEquals("success", this.action.submit(context, c, messageContext).getId()); + } + + @Test + public void verifySuccessfulAuthenticationWithNoServiceAndWarn() + throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + final MockRequestContext context = new MockRequestContext(); + + WebUtils.putLoginTicket(context, "LOGIN"); + request.addParameter("lt", "LOGIN"); + + request.addParameter("username", "test"); + request.addParameter("password", "test"); + request.addParameter("warn", "true"); + + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, response)); + final Credential c = TestUtils.getCredentialsWithSameUsernameAndPassword(); + putCredentialInRequestScope(context, c); + + final MessageContext messageContext = mock(MessageContext.class); + assertEquals("success", this.action.submit(context, c, messageContext).getId()); + assertNotNull(WebUtils.getTicketGrantingTicketId(context)); + assertNotNull(response.getCookie(this.warnCookieGenerator.getCookieName())); + } + + @Test + public void verifySuccessfulAuthenticationWithServiceAndWarn() + throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + final MockRequestContext context = new MockRequestContext(); + + WebUtils.putLoginTicket(context, "LOGIN"); + request.addParameter("lt", "LOGIN"); + request.addParameter("username", "test"); + request.addParameter("password", "test"); + request.addParameter("warn", "true"); + request.addParameter("service", "test"); + + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, response)); + final Credential c = TestUtils.getCredentialsWithSameUsernameAndPassword(); + putCredentialInRequestScope(context, c); + + final MessageContext messageContext = mock(MessageContext.class); + assertEquals("success", this.action.submit(context, c, messageContext).getId()); + assertNotNull(response.getCookie(this.warnCookieGenerator.getCookieName())); + } + + @Test + public void verifyFailedAuthenticationWithNoService() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockRequestContext context = new MockRequestContext(); + + request.addParameter("username", "test"); + request.addParameter("password", "test2"); + + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + + final Credential c = TestUtils.getCredentialsWithSameUsernameAndPassword(); + putCredentialInRequestScope(context, c); + + context.getRequestScope().put( + "org.springframework.validation.BindException.credentials", + new BindException(c, "credentials")); + + final MessageContext messageContext = mock(MessageContext.class); + assertEquals("error", this.action.submit(context, c, messageContext).getId()); + } + + @Test + public void verifyRenewWithServiceAndSameCredentials() throws Exception { + final Credential c = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket(c); + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockRequestContext context = new MockRequestContext(); + + WebUtils.putTicketGrantingTicketInScopes(context, ticketGrantingTicket); + WebUtils.putLoginTicket(context, "LOGIN"); + request.addParameter("lt", "LOGIN"); + + request.addParameter("renew", "true"); + request.addParameter("service", "test"); + request.addParameter("username", "test"); + request.addParameter("password", "test"); + + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + context.getFlowScope().put("service", TestUtils.getService()); + + final MessageContext messageContext = mock(MessageContext.class); + assertEquals("warn", this.action.submit(context, c, messageContext).getId()); + } + + @Test + public void verifyRenewWithServiceAndDifferentCredentials() throws Exception { + final Credential c = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket(c); + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockRequestContext context = new MockRequestContext(); + + WebUtils.putLoginTicket(context, "LOGIN"); + request.addParameter("lt", "LOGIN"); + + WebUtils.putTicketGrantingTicketInScopes(context, ticketGrantingTicket); + request.addParameter("renew", "true"); + request.addParameter("service", "test"); + request.addParameter("username", "test2"); + request.addParameter("password", "test2"); + + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + + final MessageContext messageContext = mock(MessageContext.class); + assertEquals("success", this.action.submit(context, c, messageContext).getId()); + } + + @Test + public void verifyRenewWithServiceAndBadCredentials() throws Exception { + final Credential c = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final TicketGrantingTicket ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket(c); + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockRequestContext context = new MockRequestContext(); + + WebUtils.putTicketGrantingTicketInScopes(context, ticketGrantingTicket); + request.addParameter("renew", "true"); + request.addParameter("service", "test"); + + final Credential c2 = TestUtils.getCredentialsWithDifferentUsernameAndPassword(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + putCredentialInRequestScope(context, c2); + context.getRequestScope().put( + "org.springframework.validation.BindException.credentials", + new BindException(c2, "credentials")); + + final MessageContext messageContext = mock(MessageContext.class); + assertEquals("error", this.action.submit(context, c2, messageContext).getId()); + } + + + /** + * Put credentials in request scope. + * + * @param context the context + * @param c the credential + */ + private static void putCredentialInRequestScope( + final RequestContext context, @NotNull final Credential c) { + context.getRequestScope().put("credentials", c); + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/FrontChannelLogoutActionTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/FrontChannelLogoutActionTests.java new file mode 100644 index 000000000000..16e4e8abcd1b --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/FrontChannelLogoutActionTests.java @@ -0,0 +1,198 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.zip.Inflater; + +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl; +import org.jasig.cas.authentication.principal.SingleLogoutService; +import org.jasig.cas.logout.LogoutManager; +import org.jasig.cas.logout.LogoutManagerImpl; +import org.jasig.cas.logout.DefaultLogoutRequest; +import org.jasig.cas.logout.LogoutRequest; +import org.jasig.cas.logout.LogoutRequestStatus; +import org.jasig.cas.logout.SamlCompliantLogoutMessageCreator; +import org.jasig.cas.mock.MockTicketGrantingTicket; +import org.jasig.cas.services.DefaultRegisteredServiceAccessStrategy; +import org.jasig.cas.services.LogoutType; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.RegisteredServiceImpl; +import org.jasig.cas.services.ServicesManager; + +import org.jasig.cas.util.CompressionUtils; +import org.jasig.cas.util.http.SimpleHttpClientFactoryBean; +import org.jasig.cas.web.support.WebUtils; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.core.collection.LocalAttributeMap; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; +import org.springframework.webflow.test.MockFlowExecutionContext; +import org.springframework.webflow.test.MockFlowExecutionKey; + +/** + * @author Jerome Leleu + * @since 4.0.0 + */ +public class FrontChannelLogoutActionTests { + + private static final String FLOW_EXECUTION_KEY = "12234"; + + private static final String TICKET_ID = "ST-XXX"; + + private static final String TEST_URL = "https://www.apereo.org"; + + private FrontChannelLogoutAction frontChannelLogoutAction; + + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + + private RequestContext requestContext; + + @Mock + private ServicesManager servicesManager; + + private LogoutManager logoutManager; + + public FrontChannelLogoutActionTests() { + MockitoAnnotations.initMocks(this); + } + + @Before + public void onSetUp() throws Exception { + + this.logoutManager = new LogoutManagerImpl(this.servicesManager, + new SimpleHttpClientFactoryBean().getObject(), new SamlCompliantLogoutMessageCreator()); + this.frontChannelLogoutAction = new FrontChannelLogoutAction(this.logoutManager); + + this.request = new MockHttpServletRequest(); + this.response = new MockHttpServletResponse(); + this.requestContext = mock(RequestContext.class); + final ServletExternalContext servletExternalContext = mock(ServletExternalContext.class); + when(this.requestContext.getExternalContext()).thenReturn(servletExternalContext); + when(servletExternalContext.getNativeRequest()).thenReturn(request); + when(servletExternalContext.getNativeResponse()).thenReturn(response); + final LocalAttributeMap flowScope = new LocalAttributeMap(); + when(this.requestContext.getFlowScope()).thenReturn(flowScope); + final MockFlowExecutionKey mockFlowExecutionKey = new MockFlowExecutionKey(FLOW_EXECUTION_KEY); + final MockFlowExecutionContext mockFlowExecutionContext = new MockFlowExecutionContext(); + mockFlowExecutionContext.setKey(mockFlowExecutionKey); + when(this.requestContext.getFlowExecutionContext()).thenReturn(mockFlowExecutionContext); + } + + @Test + public void verifyLogoutNoRequest() throws Exception { + this.requestContext.getFlowScope().put(FrontChannelLogoutAction.LOGOUT_INDEX, 0); + final Event event = this.frontChannelLogoutAction.doExecute(this.requestContext); + assertEquals(FrontChannelLogoutAction.FINISH_EVENT, event.getId()); + } + + @Test + public void verifyLogoutNoIndex() throws Exception { + WebUtils.putLogoutRequests(this.requestContext, Collections.emptyList()); + final Event event = this.frontChannelLogoutAction.doExecute(this.requestContext); + assertEquals(FrontChannelLogoutAction.FINISH_EVENT, event.getId()); + } + + @Test + public void verifyLogoutOneLogoutRequestSuccess() throws Exception { + final DefaultLogoutRequest logoutRequest = new DefaultLogoutRequest("", null, null); + logoutRequest.setStatus(LogoutRequestStatus.SUCCESS); + WebUtils.putLogoutRequests(this.requestContext, Collections.emptyList()); + this.requestContext.getFlowScope().put(FrontChannelLogoutAction.LOGOUT_INDEX, 0); + final Event event = this.frontChannelLogoutAction.doExecute(this.requestContext); + assertEquals(FrontChannelLogoutAction.FINISH_EVENT, event.getId()); + } + + @Test + public void verifyLogoutOneLogoutRequestNotAttempted() throws Exception { + final LogoutRequest logoutRequest = new DefaultLogoutRequest(TICKET_ID, + new SimpleWebApplicationServiceImpl(TEST_URL), + new URL(TEST_URL)); + final Event event = getLogoutEvent(Arrays.asList(logoutRequest)); + + assertEquals(FrontChannelLogoutAction.REDIRECT_APP_EVENT, event.getId()); + final List list = WebUtils.getLogoutRequests(this.requestContext); + assertEquals(1, list.size()); + final String url = (String) event.getAttributes().get(FrontChannelLogoutAction.DEFAULT_FLOW_ATTRIBUTE_LOGOUT_URL); + assertTrue(url.startsWith(TEST_URL + "?" + FrontChannelLogoutAction.DEFAULT_LOGOUT_PARAMETER + "=")); + final byte[] samlMessage = CompressionUtils.decodeBase64ToByteArray( + URLDecoder.decode(StringUtils.substringAfter(url, "?" + FrontChannelLogoutAction.DEFAULT_LOGOUT_PARAMETER + "="), "UTF-8")); + final Inflater decompresser = new Inflater(); + decompresser.setInput(samlMessage); + final byte[] result = new byte[1000]; + decompresser.inflate(result); + decompresser.end(); + final String message = new String(result); + assertTrue(message.startsWith("" + TICKET_ID + "")); + } + + @Test + public void verifyLogoutUrlForServiceIsUsed() throws Exception { + final RegisteredService svc = getRegisteredService(); + when(this.servicesManager.findServiceBy(any(SingleLogoutService.class))).thenReturn(svc); + + final SingleLogoutService service = mock(SingleLogoutService.class); + when(service.getId()).thenReturn(svc.getServiceId()); + when(service.getOriginalUrl()).thenReturn(svc.getServiceId()); + + final MockTicketGrantingTicket tgt = new MockTicketGrantingTicket("test"); + tgt.getServices().put("service", service); + final Event event = getLogoutEvent(this.logoutManager.performLogout(tgt)); + assertEquals(FrontChannelLogoutAction.REDIRECT_APP_EVENT, event.getId()); + final List list = WebUtils.getLogoutRequests(this.requestContext); + assertEquals(1, list.size()); + final String url = (String) event.getAttributes().get(FrontChannelLogoutAction.DEFAULT_FLOW_ATTRIBUTE_LOGOUT_URL); + assertTrue(url.startsWith(svc.getLogoutUrl().toExternalForm())); + + } + + private RegisteredService getRegisteredService() throws MalformedURLException { + final RegisteredServiceImpl svc = new RegisteredServiceImpl(); + svc.setServiceId("https://www.github.com"); + svc.setLogoutUrl(new URL("https://www.google.com")); + svc.setName("Service logout test"); + svc.setLogoutType(LogoutType.FRONT_CHANNEL); + svc.setAccessStrategy(new DefaultRegisteredServiceAccessStrategy(true, true)); + return svc; + } + + private Event getLogoutEvent(final List requests) throws Exception { + WebUtils.putLogoutRequests(this.requestContext, requests); + this.requestContext.getFlowScope().put(FrontChannelLogoutAction.LOGOUT_INDEX, 0); + return this.frontChannelLogoutAction.doExecute(this.requestContext); + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/GenerateServiceTicketActionTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/GenerateServiceTicketActionTests.java new file mode 100644 index 000000000000..7f690ca025a1 --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/GenerateServiceTicketActionTests.java @@ -0,0 +1,137 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.AbstractCentralAuthenticationServiceTest; +import org.jasig.cas.TestUtils; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.web.support.WebUtils; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.test.MockRequestContext; + +import javax.servlet.http.Cookie; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Scott Battaglia + * @since 3.0.0.4 + */ +public final class GenerateServiceTicketActionTests extends AbstractCentralAuthenticationServiceTest { + + private GenerateServiceTicketAction action; + + private TicketGrantingTicket ticketGrantingTicket; + + @Before + public void onSetUp() throws Exception { + this.action = new GenerateServiceTicketAction(); + this.action.setCentralAuthenticationService(getCentralAuthenticationService()); + this.action.afterPropertiesSet(); + + this.ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + } + + @Test + public void verifyServiceTicketFromCookie() throws Exception { + final MockRequestContext context = new MockRequestContext(); + context.getFlowScope().put("service", TestUtils.getService()); + context.getFlowScope().put("ticketGrantingTicketId", this.ticketGrantingTicket.getId()); + final MockHttpServletRequest request = new MockHttpServletRequest(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + request.addParameter("service", "service"); + request.setCookies(new Cookie("TGT", this.ticketGrantingTicket.getId())); + + this.action.execute(context); + + assertNotNull(WebUtils.getServiceTicketFromRequestScope(context)); + } + + @Test + public void verifyTicketGrantingTicketFromRequest() throws Exception { + final MockRequestContext context = new MockRequestContext(); + context.getFlowScope().put("service", TestUtils.getService()); + final MockHttpServletRequest request = new MockHttpServletRequest(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + request.addParameter("service", "service"); + WebUtils.putTicketGrantingTicketInScopes(context, + this.ticketGrantingTicket); + + this.action.execute(context); + + assertNotNull(WebUtils.getServiceTicketFromRequestScope(context)); + } + + @Test + public void verifyTicketGrantingTicketNoTgt() throws Exception { + final MockRequestContext context = new MockRequestContext(); + context.getFlowScope().put("service", TestUtils.getService()); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + request.addParameter("service", "service"); + + final TicketGrantingTicket tgt = mock(TicketGrantingTicket.class); + when(tgt.getId()).thenReturn("bleh"); + WebUtils.putTicketGrantingTicketInScopes(context, tgt); + + assertEquals("error", this.action.execute(context).getId()); + } + + @Test + public void verifyTicketGrantingTicketExpiredTgt() throws Exception { + final MockRequestContext context = new MockRequestContext(); + context.getFlowScope().put("service", TestUtils.getService()); + final MockHttpServletRequest request = new MockHttpServletRequest(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + request.addParameter("service", "service"); + WebUtils.putTicketGrantingTicketInScopes(context, this.ticketGrantingTicket); + + this.ticketGrantingTicket.markTicketExpired(); + assertEquals("error", this.action.execute(context).getId()); + } + + @Test + public void verifyTicketGrantingTicketNotTgtButGateway() throws Exception { + final MockRequestContext context = new MockRequestContext(); + context.getFlowScope().put("service", TestUtils.getService()); + final MockHttpServletRequest request = new MockHttpServletRequest(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + request.addParameter("service", "service"); + request.addParameter("gateway", "true"); + final TicketGrantingTicket tgt = mock(TicketGrantingTicket.class); + when(tgt.getId()).thenReturn("bleh"); + WebUtils.putTicketGrantingTicketInScopes(context, tgt); + + + assertEquals("gateway", this.action.execute(context).getId()); + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/GenericSuccessViewActionTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/GenericSuccessViewActionTests.java new file mode 100644 index 000000000000..af61463bbfc5 --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/GenericSuccessViewActionTests.java @@ -0,0 +1,67 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.NullPrincipal; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.ticket.InvalidTicketException; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Tests for {@link GenericSuccessViewAction} + * @author Misagh Moayyed + * @since 4.1.0 + */ +public class GenericSuccessViewActionTests { + + @Test + public void verifyValidPrincipal() throws InvalidTicketException { + final CentralAuthenticationService cas = mock(CentralAuthenticationService.class); + final Authentication authn = mock(Authentication.class); + when(authn.getPrincipal()).thenReturn(TestUtils.getPrincipal("cas")); + final TicketGrantingTicket tgt = mock(TicketGrantingTicket.class); + when(tgt.getAuthentication()).thenReturn(authn); + + + + when(cas.getTicket(any(String.class), any(Ticket.class.getClass()))).thenReturn(tgt); + final GenericSuccessViewAction action = new GenericSuccessViewAction(cas); + final Principal p = action.getAuthenticationPrincipal("TGT-1"); + assertNotNull(p); + assertEquals(p.getId(), "cas"); + } + + @Test + public void verifyPrincipalCanNotBeDetemined() throws InvalidTicketException { + final CentralAuthenticationService cas = mock(CentralAuthenticationService.class); + when(cas.getTicket(any(String.class), any(Ticket.class.getClass()))).thenThrow(new InvalidTicketException("TGT-1")); + final GenericSuccessViewAction action = new GenericSuccessViewAction(cas); + final Principal p = action.getAuthenticationPrincipal("TGT-1"); + assertNotNull(p); + assertTrue(p instanceof NullPrincipal); + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/InitialFlowSetupActionTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/InitialFlowSetupActionTests.java new file mode 100644 index 000000000000..93db4fc967ce --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/InitialFlowSetupActionTests.java @@ -0,0 +1,147 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.web.support.ArgumentExtractor; +import org.jasig.cas.web.support.CasArgumentExtractor; +import org.jasig.cas.web.support.CookieRetrievingCookieGenerator; +import org.jasig.cas.web.support.WebUtils; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.repository.NoSuchFlowExecutionException; +import org.springframework.webflow.test.MockRequestContext; + +import java.util.Arrays; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * + * @author Scott Battaglia + * @since 3.0.0.5 + * + */ +public class InitialFlowSetupActionTests { + private static final String CONST_CONTEXT_PATH = "/test"; + private static final String CONST_CONTEXT_PATH_2 = "/test1"; + + private final InitialFlowSetupAction action = new InitialFlowSetupAction(); + + private CookieRetrievingCookieGenerator warnCookieGenerator; + + private CookieRetrievingCookieGenerator tgtCookieGenerator; + + private ServicesManager servicesManager; + + @Before + public void setUp() throws Exception { + this.warnCookieGenerator = new CookieRetrievingCookieGenerator(); + this.tgtCookieGenerator = new CookieRetrievingCookieGenerator(); + this.action.setTicketGrantingTicketCookieGenerator(this.tgtCookieGenerator); + this.action.setWarnCookieGenerator(this.warnCookieGenerator); + final ArgumentExtractor[] argExtractors = new ArgumentExtractor[] {new CasArgumentExtractor()}; + this.action.setArgumentExtractors(Arrays.asList(argExtractors)); + + this.servicesManager = mock(ServicesManager.class); + when(this.servicesManager.findServiceBy(any(Service.class))).thenReturn(TestUtils.getRegisteredService("test")); + this.action.setServicesManager(this.servicesManager); + + this.action.afterPropertiesSet(); + } + + @Test + public void verifySettingContextPath() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setContextPath(CONST_CONTEXT_PATH); + final MockRequestContext context = new MockRequestContext(); + context.setExternalContext(new ServletExternalContext(new MockServletContext(), request, new MockHttpServletResponse())); + + this.action.doExecute(context); + + assertEquals(CONST_CONTEXT_PATH + "/", this.warnCookieGenerator.getCookiePath()); + assertEquals(CONST_CONTEXT_PATH + "/", this.tgtCookieGenerator.getCookiePath()); + } + + @Test + public void verifyResettingContexPath() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setContextPath(CONST_CONTEXT_PATH); + final MockRequestContext context = new MockRequestContext(); + context.setExternalContext(new ServletExternalContext(new MockServletContext(), request, new MockHttpServletResponse())); + + this.action.doExecute(context); + + assertEquals(CONST_CONTEXT_PATH + "/", this.warnCookieGenerator.getCookiePath()); + assertEquals(CONST_CONTEXT_PATH + "/", this.tgtCookieGenerator.getCookiePath()); + + request.setContextPath(CONST_CONTEXT_PATH_2); + this.action.doExecute(context); + + assertNotSame(CONST_CONTEXT_PATH_2 + "/", this.warnCookieGenerator.getCookiePath()); + assertNotSame(CONST_CONTEXT_PATH_2 + "/", this.tgtCookieGenerator.getCookiePath()); + assertEquals(CONST_CONTEXT_PATH + "/", this.warnCookieGenerator.getCookiePath()); + assertEquals(CONST_CONTEXT_PATH + "/", this.tgtCookieGenerator.getCookiePath()); + } + + @Test + public void verifyNoServiceFound() throws Exception { + final MockRequestContext context = new MockRequestContext(); + context.setExternalContext(new ServletExternalContext(new MockServletContext(), new MockHttpServletRequest(), + new MockHttpServletResponse())); + + final Event event = this.action.execute(context); + + assertNull(WebUtils.getService(context)); + + assertEquals("success", event.getId()); + } + + @Test + public void verifyServiceFound() throws Exception { + final MockRequestContext context = new MockRequestContext(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("service", "test"); + context.setExternalContext(new ServletExternalContext(new MockServletContext(), request, new MockHttpServletResponse())); + + final Event event = this.action.execute(context); + + assertEquals("test", WebUtils.getService(context).getId()); + assertNotNull(WebUtils.getRegisteredService(context)); + assertEquals("success", event.getId()); + } + + @Test(expected = NoSuchFlowExecutionException.class) + public void disableFlowIfNoService() throws Exception { + this.action.setEnableFlowOnAbsentServiceRequest(false); + final MockRequestContext context = new MockRequestContext(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + context.setExternalContext(new ServletExternalContext(new MockServletContext(), request, new MockHttpServletResponse())); + + this.action.execute(context); + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/LogoutActionTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/LogoutActionTests.java new file mode 100644 index 000000000000..dca96f137fd6 --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/LogoutActionTests.java @@ -0,0 +1,171 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.AbstractCentralAuthenticationServiceTest; +import org.jasig.cas.logout.DefaultLogoutRequest; +import org.jasig.cas.logout.LogoutRequest; +import org.jasig.cas.logout.LogoutRequestStatus; +import org.jasig.cas.services.DefaultServicesManagerImpl; +import org.jasig.cas.services.InMemoryServiceRegistryDaoImpl; +import org.jasig.cas.services.RegisteredServiceImpl; +import org.jasig.cas.web.support.CookieRetrievingCookieGenerator; +import org.jasig.cas.web.support.WebUtils; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.core.collection.LocalAttributeMap; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; + +import javax.servlet.http.Cookie; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public class LogoutActionTests extends AbstractCentralAuthenticationServiceTest { + + private static final String COOKIE_TGC_ID = "CASTGC"; + + private LogoutAction logoutAction; + + private CookieRetrievingCookieGenerator warnCookieGenerator; + + private CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator; + + private InMemoryServiceRegistryDaoImpl serviceRegistryDao; + + private DefaultServicesManagerImpl serviceManager; + + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + + private RequestContext requestContext; + + @Before + public void onSetUp() throws Exception { + this.request = new MockHttpServletRequest(); + this.response = new MockHttpServletResponse(); + this.requestContext = mock(RequestContext.class); + final ServletExternalContext servletExternalContext = mock(ServletExternalContext.class); + when(this.requestContext.getExternalContext()).thenReturn(servletExternalContext); + when(servletExternalContext.getNativeRequest()).thenReturn(request); + when(servletExternalContext.getNativeResponse()).thenReturn(response); + final LocalAttributeMap flowScope = new LocalAttributeMap(); + when(this.requestContext.getFlowScope()).thenReturn(flowScope); + + this.warnCookieGenerator = new CookieRetrievingCookieGenerator(); + this.serviceRegistryDao = new InMemoryServiceRegistryDaoImpl(); + this.serviceManager = new DefaultServicesManagerImpl(serviceRegistryDao); + this.serviceManager.reload(); + + this.warnCookieGenerator.setCookieName("test"); + + this.ticketGrantingTicketCookieGenerator = new CookieRetrievingCookieGenerator(); + this.ticketGrantingTicketCookieGenerator.setCookieName(COOKIE_TGC_ID); + + this.logoutAction = new LogoutAction(); + this.logoutAction.setServicesManager(this.serviceManager); + } + + @Test + public void verifyLogoutNoCookie() throws Exception { + final Event event = this.logoutAction.doExecute(this.requestContext); + assertEquals(LogoutAction.FINISH_EVENT, event.getId()); + } + + @Test + public void verifyLogoutForServiceWithFollowRedirectsAndMatchingService() throws Exception { + this.request.addParameter("service", "TestService"); + final RegisteredServiceImpl impl = new RegisteredServiceImpl(); + impl.setServiceId("TestService"); + impl.setName("TestService"); + this.serviceManager.save(impl); + this.logoutAction.setFollowServiceRedirects(true); + final Event event = this.logoutAction.doExecute(this.requestContext); + assertEquals(LogoutAction.FINISH_EVENT, event.getId()); + assertEquals("TestService", this.requestContext.getFlowScope().get("logoutRedirectUrl")); + } + + @Test + public void logoutForServiceWithNoFollowRedirects() throws Exception { + this.request.addParameter("service", "TestService"); + this.logoutAction.setFollowServiceRedirects(false); + final Event event = this.logoutAction.doExecute(this.requestContext); + assertEquals(LogoutAction.FINISH_EVENT, event.getId()); + assertNull(this.requestContext.getFlowScope().get("logoutRedirectUrl")); + } + + @Test + public void logoutForServiceWithFollowRedirectsNoAllowedService() throws Exception { + this.request.addParameter("service", "TestService"); + final RegisteredServiceImpl impl = new RegisteredServiceImpl(); + impl.setServiceId("http://FooBar"); + impl.setName("FooBar"); + this.serviceManager.save(impl); + this.logoutAction.setFollowServiceRedirects(true); + final Event event = this.logoutAction.doExecute(this.requestContext); + assertEquals(LogoutAction.FINISH_EVENT, event.getId()); + assertNull(this.requestContext.getFlowScope().get("logoutRedirectUrl")); + } + + @Test + public void verifyLogoutCookie() throws Exception { + final Cookie cookie = new Cookie(COOKIE_TGC_ID, "test"); + this.request.setCookies(cookie); + final Event event = this.logoutAction.doExecute(this.requestContext); + assertEquals(LogoutAction.FINISH_EVENT, event.getId()); + } + + @Test + public void verifyLogoutRequestBack() throws Exception { + final Cookie cookie = new Cookie(COOKIE_TGC_ID, "test"); + this.request.setCookies(cookie); + final LogoutRequest logoutRequest = new DefaultLogoutRequest("", null, null); + logoutRequest.setStatus(LogoutRequestStatus.SUCCESS); + WebUtils.putLogoutRequests(this.requestContext, Arrays.asList(logoutRequest)); + final Event event = this.logoutAction.doExecute(this.requestContext); + assertEquals(LogoutAction.FINISH_EVENT, event.getId()); + } + + @SuppressWarnings("unchecked") + @Test + public void verifyLogoutRequestFront() throws Exception { + final Cookie cookie = new Cookie(COOKIE_TGC_ID, "test"); + this.request.setCookies(cookie); + final LogoutRequest logoutRequest = new DefaultLogoutRequest("", null, null); + WebUtils.putLogoutRequests(this.requestContext, Arrays.asList(logoutRequest)); + final Event event = this.logoutAction.doExecute(this.requestContext); + assertEquals(LogoutAction.FRONT_EVENT, event.getId()); + final List logoutRequests = WebUtils.getLogoutRequests(this.requestContext); + assertEquals(1, logoutRequests.size()); + assertEquals(logoutRequest, logoutRequests.get(0)); + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/SendTicketGrantingTicketActionTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/SendTicketGrantingTicketActionTests.java new file mode 100644 index 000000000000..4f435bf6ce4d --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/SendTicketGrantingTicketActionTests.java @@ -0,0 +1,140 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.AbstractCentralAuthenticationServiceTest; +import org.jasig.cas.CasProtocolConstants; +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.web.support.CookieRetrievingCookieGenerator; +import org.jasig.cas.web.support.WebUtils; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.test.MockRequestContext; + +import javax.servlet.http.Cookie; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Marvin S. Addison + * @since 3.4.0 + */ +public class SendTicketGrantingTicketActionTests extends AbstractCentralAuthenticationServiceTest { + private SendTicketGrantingTicketAction action; + private CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator; + private MockRequestContext context; + + @Before + public void onSetUp() throws Exception { + + this.ticketGrantingTicketCookieGenerator = new CookieRetrievingCookieGenerator(); + ticketGrantingTicketCookieGenerator.setCookieName("TGT"); + + this.action = new SendTicketGrantingTicketAction(ticketGrantingTicketCookieGenerator, + getCentralAuthenticationService(), getServicesManager()); + this.action.setServicesManager(getServicesManager()); + this.action.setCreateSsoSessionCookieOnRenewAuthentications(true); + this.action.afterPropertiesSet(); + + this.context = new MockRequestContext(); + } + + @Test + public void verifyNoTgtToSet() throws Exception { + this.context.setExternalContext(new ServletExternalContext(new MockServletContext(), + new MockHttpServletRequest(), new MockHttpServletResponse())); + + assertEquals("success", this.action.execute(this.context).getId()); + } + + @Test + public void verifyTgtToSet() throws Exception { + final MockHttpServletResponse response = new MockHttpServletResponse(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + final TicketGrantingTicket tgt = mock(TicketGrantingTicket.class); + when(tgt.getId()).thenReturn("test"); + + WebUtils.putTicketGrantingTicketInScopes(this.context, tgt); + this.context.setExternalContext(new ServletExternalContext(new MockServletContext(), + request, response)); + + assertEquals("success", this.action.execute(this.context).getId()); + request.setCookies(response.getCookies()); + assertEquals(tgt.getId(), this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request)); + } + + @Test + public void verifyTgtToSetRemovingOldTgt() throws Exception { + final MockHttpServletResponse response = new MockHttpServletResponse(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + + final TicketGrantingTicket tgt = mock(TicketGrantingTicket.class); + when(tgt.getId()).thenReturn("test"); + + request.setCookies(new Cookie("TGT", "test5")); + WebUtils.putTicketGrantingTicketInScopes(this.context, tgt); + this.context.setExternalContext(new ServletExternalContext(new MockServletContext(), request, response)); + + assertEquals("success", this.action.execute(this.context).getId()); + request.setCookies(response.getCookies()); + assertEquals(tgt.getId(), this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request)); + } + + @Test + public void verifySsoSessionCookieOnRenewAsParameter() throws Exception { + final MockHttpServletResponse response = new MockHttpServletResponse(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter(CasProtocolConstants.PARAMETER_RENEW, "true"); + + final TicketGrantingTicket tgt = mock(TicketGrantingTicket.class); + when(tgt.getId()).thenReturn("test"); + request.setCookies(new Cookie("TGT", "test5")); + WebUtils.putTicketGrantingTicketInScopes(this.context, tgt); + this.context.setExternalContext(new ServletExternalContext(new MockServletContext(), request, response)); + + this.action.setCreateSsoSessionCookieOnRenewAuthentications(false); + assertEquals("success", this.action.execute(this.context).getId()); + assertEquals(0, response.getCookies().length); + } + + @Test + public void verifySsoSessionCookieOnServiceSsoDisallowed() throws Exception { + final MockHttpServletResponse response = new MockHttpServletResponse(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + + final WebApplicationService svc = mock(WebApplicationService.class); + when(svc.getId()).thenReturn("TestSsoFalse"); + + final TicketGrantingTicket tgt = mock(TicketGrantingTicket.class); + when(tgt.getId()).thenReturn("test"); + request.setCookies(new Cookie("TGT", "test5")); + WebUtils.putTicketGrantingTicketInScopes(this.context, tgt); + this.context.setExternalContext(new ServletExternalContext(new MockServletContext(), request, response)); + this.context.getFlowScope().put("service", svc); + this.action.setCreateSsoSessionCookieOnRenewAuthentications(false); + assertEquals("success", this.action.execute(this.context).getId()); + assertEquals(0, response.getCookies().length); + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/ServiceAuthorizationCheckTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/ServiceAuthorizationCheckTests.java new file mode 100644 index 000000000000..d94c1397c083 --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/ServiceAuthorizationCheckTests.java @@ -0,0 +1,109 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.flow; + +import java.util.ArrayList; +import java.util.List; + +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.RegisteredServiceImpl; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.services.UnauthorizedServiceException; +import org.jasig.cas.services.DefaultRegisteredServiceAccessStrategy; +import org.junit.Before; +import org.junit.Test; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.test.MockRequestContext; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Mockito based tests for @{link ServiceAuthorizationCheck} + * + * @author Dmitriy Kopylenko + * @since 3.5.0 + */ +public class ServiceAuthorizationCheckTests { + + private ServiceAuthorizationCheck serviceAuthorizationCheck; + + private final WebApplicationService authorizedService = mock(WebApplicationService.class); + + private final WebApplicationService unauthorizedService = mock(WebApplicationService.class); + + private final WebApplicationService undefinedService = mock(WebApplicationService.class); + + private final ServicesManager servicesManager = mock(ServicesManager.class); + + + @Before + public void setUpMocks() { + final RegisteredServiceImpl authorizedRegisteredService = new RegisteredServiceImpl(); + final RegisteredServiceImpl unauthorizedRegisteredService = new RegisteredServiceImpl(); + unauthorizedRegisteredService.setAccessStrategy( + new DefaultRegisteredServiceAccessStrategy(false, false)); + + final List list = new ArrayList<>(); + list.add(authorizedRegisteredService); + list.add(unauthorizedRegisteredService); + + when(this.servicesManager.findServiceBy(this.authorizedService)).thenReturn(authorizedRegisteredService); + when(this.servicesManager.findServiceBy(this.unauthorizedService)).thenReturn(unauthorizedRegisteredService); + when(this.servicesManager.findServiceBy(this.undefinedService)).thenReturn(null); + + when(this.servicesManager.getAllServices()).thenReturn(list); + + this.serviceAuthorizationCheck = new ServiceAuthorizationCheck(this.servicesManager); + } + + @Test + public void noServiceProvided() throws Exception { + final MockRequestContext mockRequestContext = new MockRequestContext(); + final Event event = this.serviceAuthorizationCheck.doExecute(mockRequestContext); + assertEquals("success", event.getId()); + + } + + @Test + public void authorizedServiceProvided() throws Exception { + final MockRequestContext mockRequestContext = new MockRequestContext(); + mockRequestContext.getFlowScope().put("service", this.authorizedService); + final Event event = this.serviceAuthorizationCheck.doExecute(mockRequestContext); + assertEquals("success", event.getId()); + } + + @Test(expected=UnauthorizedServiceException.class) + public void unauthorizedServiceProvided() throws Exception { + final MockRequestContext mockRequestContext = new MockRequestContext(); + mockRequestContext.getFlowScope().put("service", this.unauthorizedService); + + this.serviceAuthorizationCheck.doExecute(mockRequestContext); + fail("Should have thrown UnauthorizedServiceException"); + } + + @Test(expected=UnauthorizedServiceException.class) + public void serviceThatIsNotRegisteredProvided() throws Exception { + final MockRequestContext mockRequestContext = new MockRequestContext(); + mockRequestContext.getFlowScope().put("service", this.undefinedService); + this.serviceAuthorizationCheck.doExecute(mockRequestContext); + fail("Should have thrown UnauthorizedServiceException"); + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/TicketGrantingTicketCheckActionTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/TicketGrantingTicketCheckActionTests.java new file mode 100644 index 000000000000..ab65e06e5537 --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/flow/TicketGrantingTicketCheckActionTests.java @@ -0,0 +1,78 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.web.flow; + +import org.jasig.cas.AbstractCentralAuthenticationServiceTest; +import org.jasig.cas.TestUtils; +import org.jasig.cas.mock.MockTicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.web.support.WebUtils; +import org.junit.Test; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.test.MockRequestContext; + +import static org.junit.Assert.*; + +/** + * Handles tests for {@link TicketGrantingTicketCheckAction}. + * + * @author Misagh Moayyed mmoayyed@unicon.net + * @since 4.1.0 + */ +public class TicketGrantingTicketCheckActionTests extends AbstractCentralAuthenticationServiceTest { + + @Test + public void verifyNullTicket() throws Exception { + + final MockRequestContext ctx = new MockRequestContext(); + final TicketGrantingTicketCheckAction action = new + TicketGrantingTicketCheckAction(this.getCentralAuthenticationService()); + final Event event = action.doExecute(ctx); + assertEquals(event.getId(), TicketGrantingTicketCheckAction.NOT_EXISTS); + } + + @Test + public void verifyInvalidTicket() throws Exception { + + final MockRequestContext ctx = new MockRequestContext(); + final MockTicketGrantingTicket tgt = new MockTicketGrantingTicket("user"); + + WebUtils.putTicketGrantingTicketInScopes(ctx, tgt); + final TicketGrantingTicketCheckAction action = new + TicketGrantingTicketCheckAction(this.getCentralAuthenticationService()); + final Event event = action.doExecute(ctx); + assertEquals(event.getId(), TicketGrantingTicketCheckAction.INVALID); + } + + @Test + public void verifyValidTicket() throws Exception { + + final MockRequestContext ctx = new MockRequestContext(); + final TicketGrantingTicket tgt = this.getCentralAuthenticationService() + .createTicketGrantingTicket(TestUtils.getCredentialsWithSameUsernameAndPassword()); + + WebUtils.putTicketGrantingTicketInScopes(ctx, tgt); + final TicketGrantingTicketCheckAction action = new + TicketGrantingTicketCheckAction(this.getCentralAuthenticationService()); + final Event event = action.doExecute(ctx); + assertEquals(event.getId(), TicketGrantingTicketCheckAction.VALID); + } + +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapterTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapterTests.java new file mode 100644 index 000000000000..46fc0d46f0a9 --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapterTests.java @@ -0,0 +1,49 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.support; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.test.MockRequestContext; + +/** + * Base class for in-memory throttled submission handlers. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +public abstract class AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapterTests +extends AbstractThrottledSubmissionHandlerInterceptorAdapterTests { + + @Override + protected MockHttpServletResponse loginUnsuccessfully(final String username, final String fromAddress) throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + request.setMethod("POST"); + request.setParameter("username", username); + request.setRemoteAddr(fromAddress); + final MockRequestContext context = new MockRequestContext(); + context.setCurrentEvent(new Event("", "error")); + request.setAttribute("flowRequestContext", context); + getThrottle().preHandle(request, response, null); + getThrottle().postHandle(request, response, null, null); + return response; + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/AbstractThrottledSubmissionHandlerInterceptorAdapterTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/AbstractThrottledSubmissionHandlerInterceptorAdapterTests.java new file mode 100644 index 000000000000..240e11518603 --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/AbstractThrottledSubmissionHandlerInterceptorAdapterTests.java @@ -0,0 +1,99 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.support; + +import org.jasig.inspektr.common.web.ClientInfo; +import org.jasig.inspektr.common.web.ClientInfoHolder; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.mock.web.MockHttpServletResponse; + +import static org.junit.Assert.assertEquals; + +/** + * Base class for submission throttle tests. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +public abstract class AbstractThrottledSubmissionHandlerInterceptorAdapterTests { + + protected static final int FAILURE_RANGE = 5; + + protected static final int FAILURE_THRESHOLD = 10; + + protected static final String IP_ADDRESS = "1.2.3.4"; + + protected static final ClientInfo CLIENT_INFO = new ClientInfo(IP_ADDRESS, IP_ADDRESS); + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + @Before + public void setUp() throws Exception { + ClientInfoHolder.setClientInfo(CLIENT_INFO); + } + + @After + public void tearDown() throws Exception { + ClientInfoHolder.setClientInfo(null); + } + + @Test + public void verifyThrottle() throws Exception { + final double rate = (double) FAILURE_THRESHOLD / (double) FAILURE_RANGE; + getThrottle().setFailureRangeInSeconds(FAILURE_RANGE); + getThrottle().setFailureThreshold(FAILURE_THRESHOLD); + getThrottle().afterPropertiesSet(); + + // Ensure that repeated logins BELOW threshold rate are allowed + // Wait 7% more than threshold period + int wait = (int) (1000.0 * 1.07 / rate); + failLoop(3, wait, 200); + + // Ensure that repeated logins ABOVE threshold rate are throttled + // Wait 7% less than threshold period + wait = (int) (1000.0 * 0.93 / rate); + failLoop(3, wait, 403); + + // Ensure that slowing down relieves throttle + // Wait 7% more than threshold period + wait = (int) (1000.0 * 1.07 / rate); + Thread.sleep(wait); + failLoop(3, wait, 200); + } + + + private void failLoop(final int trials, final int period, final int expected) throws Exception { + // Seed with something to compare against + loginUnsuccessfully("mog", "1.2.3.4").getStatus(); + for (int i = 0; i < trials; i++) { + logger.debug("Waiting for {} ms", period); + Thread.sleep(period); + assertEquals(expected, loginUnsuccessfully("mog", "1.2.3.4").getStatus()); + } + } + + + protected abstract MockHttpServletResponse loginUnsuccessfully(final String username, final String fromAddress) throws Exception; + + protected abstract AbstractThrottledSubmissionHandlerInterceptorAdapter getThrottle(); +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/CookieRetrievingCookieGeneratorTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/CookieRetrievingCookieGeneratorTests.java new file mode 100644 index 000000000000..2ebdc8f930c7 --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/CookieRetrievingCookieGeneratorTests.java @@ -0,0 +1,85 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.support; + +import org.jasig.cas.authentication.RememberMeCredential; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import javax.servlet.http.Cookie; + +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for {@link org.jasig.cas.web.support.CookieRetrievingCookieGenerator}. + * @author Scott Battaglia + * @since 3.2.1 + * + */ +public final class CookieRetrievingCookieGeneratorTests { + + private CookieRetrievingCookieGenerator generator; + + @Before + public void setUp() throws Exception { + this.generator = new CookieRetrievingCookieGenerator(); + this.generator.setRememberMeMaxAge(100); + this.generator.setCookieDomain("cas.org"); + this.generator.setCookieMaxAge(5); + this.generator.setCookiePath("/"); + this.generator.setCookieName("test"); + } + + @Test + public void verifyCookieAddWithRememberMe() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter(RememberMeCredential.REQUEST_PARAMETER_REMEMBER_ME, "true"); + final MockHttpServletResponse response = new MockHttpServletResponse(); + + this.generator.addCookie(request, response, "test"); + request.setCookies(response.getCookies()); + + final Cookie c = response.getCookie("test"); + assertEquals(100, c.getMaxAge()); + assertEquals("test", this.generator.retrieveCookieValue(request)); + } + + @Test + public void verifyCookieAddWithoutRememberMe() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + this.generator.addCookie(request, response, "test"); + request.setCookies(response.getCookies()); + + final Cookie c = response.getCookie("test"); + assertEquals(5, c.getMaxAge()); + assertEquals("test", this.generator.retrieveCookieValue(request)); + } + + @Test + public void verifyCookieRetrieve() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + this.generator.addCookie(request, response, "test"); + request.setCookies(response.getCookies()); + assertEquals("test", this.generator.retrieveCookieValue(request)); + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/DefaultCasCookieValueManagerTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/DefaultCasCookieValueManagerTests.java new file mode 100644 index 000000000000..0a4dc2a81036 --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/DefaultCasCookieValueManagerTests.java @@ -0,0 +1,95 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.jasig.cas.web.support; + +import org.jasig.cas.util.DefaultCipherExecutor; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +import javax.servlet.http.Cookie; + +import static org.junit.Assert.*; + +/** + * Test cases for {@link DefaultCasCookieValueManagerTests}. + * + * @author Misagh Moayyed + * @since 4.1 + */ +public class DefaultCasCookieValueManagerTests { + + private static final String ENC_KEY = "1PbwSbnHeinpkZOSZjuSJ8yYpUrInm5aaV18J2Ar4rM"; + private static final String SIGN_KEY = "szxK-5_eJjs-aUj-64MpUZ-GPPzGLhYPLGl0wrYjYNVAGva2P0lLe6UGKGM7k8dWxsOVGutZWgvmY3l5oVPO3w"; + + @Test(expected = IllegalStateException.class) + public void defaultCookieWithNoRemote() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRemoteAddr(null); + final DefaultCasCookieValueManager mgmr = new DefaultCasCookieValueManager(new DefaultCipherExecutor(ENC_KEY, SIGN_KEY)); + mgmr.buildCookieValue("cas", request); + } + + @Test(expected = IllegalStateException.class) + public void defaultCookieWithNoAgent() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final DefaultCasCookieValueManager mgmr = new DefaultCasCookieValueManager(new DefaultCipherExecutor(ENC_KEY, SIGN_KEY)); + mgmr.buildCookieValue("cas", request); + } + + @Test + public void defaultCookieGood() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("user-agent", "the agent"); + final DefaultCasCookieValueManager mgmr = new DefaultCasCookieValueManager(new DefaultCipherExecutor(ENC_KEY, SIGN_KEY)); + assertNotNull(mgmr.buildCookieValue("cas", request)); + } + + @Test + public void defaultCookieVerify() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("user-agent", "the agent"); + final DefaultCasCookieValueManager mgmr = new DefaultCasCookieValueManager(new DefaultCipherExecutor(ENC_KEY, SIGN_KEY)); + final String c = mgmr.buildCookieValue("cas", request); + assertEquals("cas", mgmr.obtainCookieValue(new Cookie("test", c), request)); + } + + @Test(expected = IllegalStateException.class) + public void defaultCookieVerifyNoRemote() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("user-agent", "the agent"); + final DefaultCasCookieValueManager mgmr = new DefaultCasCookieValueManager(new DefaultCipherExecutor(ENC_KEY, SIGN_KEY)); + final String c = mgmr.buildCookieValue("cas", request); + request.setRemoteAddr("another ip"); + assertEquals("cas", mgmr.obtainCookieValue(new Cookie("test", c), request)); + } + + @Test(expected = IllegalStateException.class) + public void defaultCookieVerifyNoAgent() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("user-agent", "the agent"); + final DefaultCasCookieValueManager mgmr = new DefaultCasCookieValueManager(new DefaultCipherExecutor(ENC_KEY, SIGN_KEY)); + final String c = mgmr.buildCookieValue("cas", request); + + request = new MockHttpServletRequest(); + request.addHeader("user-agent", "something else"); + assertEquals("cas", mgmr.obtainCookieValue(new Cookie("test", c), request)); + } + +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapterTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapterTests.java new file mode 100644 index 000000000000..9b4062f6fa96 --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapterTests.java @@ -0,0 +1,44 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.support; + +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Unit test for {@link InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter}. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations={"classpath:/inMemoryThrottledSubmissionContext.xml"}) +public class InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapterTests +extends AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapterTests { + + @Autowired + private InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter throttle; + + @Override + protected AbstractThrottledSubmissionHandlerInterceptorAdapter getThrottle() { + return throttle; + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapterTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapterTests.java new file mode 100644 index 000000000000..dc8ea48e02e9 --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapterTests.java @@ -0,0 +1,45 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.support; + +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Unit test for {@link InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapter}. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations={"classpath:/inMemoryThrottledSubmissionContext.xml"}) +public class InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapterTests +extends AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapterTests { + + @Autowired + private InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapter throttle; + + @Override + protected AbstractThrottledSubmissionHandlerInterceptorAdapter getThrottle() { + return throttle; + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapterTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapterTests.java new file mode 100644 index 000000000000..e2afc20ab7da --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/support/InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapterTests.java @@ -0,0 +1,115 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.support; + +import org.jasig.inspektr.common.web.ClientInfo; +import org.jasig.inspektr.common.web.ClientInfoHolder; +import org.jasig.cas.authentication.AuthenticationException; +import org.jasig.cas.authentication.AuthenticationManager; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.test.MockRequestContext; + +import javax.sql.DataSource; + +import static org.junit.Assert.fail; + +/** + * Unit test for {@link InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter}. + * + * @author Marvin S. Addison + * @since 3.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { + "classpath:/core-context.xml", "classpath:/applicationContext.xml", "classpath:/jpaTestApplicationContext.xml", + "classpath:/inspektrThrottledSubmissionContext.xml" +}) +@Ignore("Disable temporarily until we have time to investigate cause of test failure") +public class InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapterTests extends +AbstractThrottledSubmissionHandlerInterceptorAdapterTests { + + @Autowired + private InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter throttle; + + @Autowired + private AuthenticationManager authenticationManager; + + @Autowired + private DataSource dataSource; + + @Override + @Before + public void setUp() throws Exception { + new JdbcTemplate(dataSource).execute("CREATE TABLE COM_AUDIT_TRAIL ( " + + "AUD_USER VARCHAR(100) NOT NULL, " + + "AUD_CLIENT_IP VARCHAR(15) NOT NULL, " + + "AUD_SERVER_IP VARCHAR(15) NOT NULL, " + + "AUD_RESOURCE VARCHAR(100) NOT NULL, " + + "AUD_ACTION VARCHAR(100) NOT NULL, " + + "APPLIC_CD VARCHAR(5) NOT NULL, " + + "AUD_DATE TIMESTAMP NOT NULL)"); + } + + @Override + protected AbstractThrottledSubmissionHandlerInterceptorAdapter getThrottle() { + return throttle; + } + + @Override + protected MockHttpServletResponse loginUnsuccessfully(final String username, final String fromAddress) + throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + request.setMethod("POST"); + request.setParameter("username", username); + request.setRemoteAddr(fromAddress); + final MockRequestContext context = new MockRequestContext(); + context.setCurrentEvent(new Event("", "error")); + request.setAttribute("flowRequestContext", context); + ClientInfoHolder.setClientInfo(new ClientInfo(request)); + + getThrottle().preHandle(request, response, null); + + try { + authenticationManager.authenticate(badCredentials(username)); + } catch (final AuthenticationException e) { + getThrottle().postHandle(request, response, null, null); + return response; + } + fail("Expected AuthenticationException"); + return null; + } + + private UsernamePasswordCredential badCredentials(final String username) { + final UsernamePasswordCredential credentials = new UsernamePasswordCredential(); + credentials.setUsername(username); + credentials.setPassword("badpassword"); + return credentials; + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/view/Cas10ResponseViewTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/view/Cas10ResponseViewTests.java new file mode 100644 index 000000000000..368945a72875 --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/view/Cas10ResponseViewTests.java @@ -0,0 +1,75 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.view; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.validation.ImmutableAssertion; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +/** + * Unit test for {@link Cas10ResponseView} class. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.0.0 + */ +public class Cas10ResponseViewTests { + + private final Cas10ResponseView view = new Cas10ResponseView(); + + private Map model; + + @Before + public void setUp() throws Exception { + this.model = new HashMap<>(); + final List list = new ArrayList<>(); + list.add(TestUtils.getAuthentication("someothername")); + this.model.put("assertion", new ImmutableAssertion( + TestUtils.getAuthentication(), list, TestUtils.getService("TestService"), true)); + } + + @Test + public void verifySuccessView() throws Exception { + final MockHttpServletResponse response = new MockHttpServletResponse(); + this.view.setSuccessResponse(true); + this.view.render(this.model, new MockHttpServletRequest(), response + ); + assertEquals("yes\ntest\n", response.getContentAsString()); + } + + @Test + public void verifyFailureView() throws Exception { + final MockHttpServletResponse response = new MockHttpServletResponse(); + this.view.setSuccessResponse(false); + this.view.render(this.model, new MockHttpServletRequest(), + response); + assertEquals("no\n\n", response.getContentAsString()); + } +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/view/Cas20ResponseViewTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/view/Cas20ResponseViewTests.java new file mode 100644 index 000000000000..972670da3765 --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/view/Cas20ResponseViewTests.java @@ -0,0 +1,70 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.view; + +import org.jasig.cas.CasProtocolConstants; +import org.jasig.cas.web.AbstractServiceValidateControllerTests; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.support.GenericWebApplicationContext; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.ViewResolver; +import org.springframework.web.servlet.support.RequestContext; +import org.springframework.web.servlet.view.JstlView; + +import java.util.Locale; + +import static org.junit.Assert.*; + + +/** + * Unit tests for {@link Cas20ResponseView}. + * @author Misagh Moayyed + * @since 4.0.0 + */ +public class Cas20ResponseViewTests extends AbstractServiceValidateControllerTests { + + @Autowired + @Qualifier("protocolCas2ViewResolver") + private ViewResolver resolver; + + @Test + public void verifyView() throws Exception { + final ModelAndView modelAndView = this.getModelAndViewUponServiceValidationWithSecurePgtUrl(); + final JstlView v = (JstlView) resolver.resolveViewName(modelAndView.getViewName(), Locale.getDefault()); + final MockHttpServletRequest req = new MockHttpServletRequest(new MockServletContext()); + v.setServletContext(req.getServletContext()); + req.setAttribute(RequestContext.WEB_APPLICATION_CONTEXT_ATTRIBUTE, + new GenericWebApplicationContext(req.getServletContext())); + + final Cas20ResponseView view = new Cas20ResponseView(v); + final MockHttpServletResponse resp = new MockHttpServletResponse(); + view.render(modelAndView.getModel(), req, resp); + + assertNotNull(req.getAttribute(CasViewConstants.MODEL_ATTRIBUTE_NAME_CHAINED_AUTHENTICATIONS)); + assertNotNull(req.getAttribute(CasViewConstants.MODEL_ATTRIBUTE_NAME_PRIMARY_AUTHENTICATION)); + assertNotNull(req.getAttribute(CasViewConstants.MODEL_ATTRIBUTE_NAME_PRINCIPAL)); + assertNotNull(req.getAttribute(CasProtocolConstants.VALIDATION_CAS_MODEL_PROXY_GRANTING_TICKET_IOU)); + } + +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/view/Cas30ResponseViewTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/view/Cas30ResponseViewTests.java new file mode 100644 index 000000000000..934f0c825f1d --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/view/Cas30ResponseViewTests.java @@ -0,0 +1,136 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.view; + +import org.jasig.cas.CasProtocolConstants; +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.UsernamePasswordCredential; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.util.CompressionUtils; +import org.jasig.cas.util.PrivateKeyFactoryBean; +import org.jasig.cas.web.AbstractServiceValidateControllerTests; +import org.jasig.cas.authentication.support.DefaultCasAttributeEncoder; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.io.ClassPathResource; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.support.GenericWebApplicationContext; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.ViewResolver; +import org.springframework.web.servlet.support.RequestContext; +import org.springframework.web.servlet.view.JstlView; + +import javax.crypto.Cipher; +import java.security.PrivateKey; +import java.util.Locale; +import java.util.Map; + +import static org.junit.Assert.*; + + +/** + * Unit tests for {@link org.jasig.cas.web.view.Cas20ResponseView}. + * @author Misagh Moayyed + * @since 4.0.0 + */ +public class Cas30ResponseViewTests extends AbstractServiceValidateControllerTests { + + @Autowired + @Qualifier("protocolCas3ViewResolver") + private ViewResolver resolver; + + @Autowired + @Qualifier("servicesManager") + private ServicesManager servicesManager; + + private Map renderView() throws Exception{ + final ModelAndView modelAndView = this.getModelAndViewUponServiceValidationWithSecurePgtUrl(); + final JstlView v = (JstlView) resolver.resolveViewName(modelAndView.getViewName(), Locale.getDefault()); + final MockHttpServletRequest req = new MockHttpServletRequest(new MockServletContext()); + v.setServletContext(req.getServletContext()); + req.setAttribute(RequestContext.WEB_APPLICATION_CONTEXT_ATTRIBUTE, + new GenericWebApplicationContext(req.getServletContext())); + + final Cas30ResponseView view = new Cas30ResponseView(v); + view.setServicesManager(this.servicesManager); + view.setCasAttributeEncoder(new DefaultCasAttributeEncoder(this.servicesManager)); + + final MockHttpServletResponse resp = new MockHttpServletResponse(); + view.render(modelAndView.getModel(), req, resp); + return (Map) req.getAttribute(CasProtocolConstants.VALIDATION_CAS_MODEL_ATTRIBUTE_NAME_ATTRIBUTES); + } + + @Test + public void verifyViewAuthnAttributes() throws Exception { + final Map attributes = renderView(); + assertTrue(attributes.containsKey(CasProtocolConstants.VALIDATION_CAS_MODEL_ATTRIBUTE_NAME_AUTHENTICATION_DATE)); + assertTrue(attributes.containsKey(CasProtocolConstants.VALIDATION_CAS_MODEL_ATTRIBUTE_NAME_FROM_NEW_LOGIN)); + assertTrue(attributes.containsKey(CasProtocolConstants.VALIDATION_REMEMBER_ME_ATTRIBUTE_NAME)); + + } + + @Test + public void verifyPasswordAsAuthenticationAttributeCanDecrypt() throws Exception { + final Map attributes = renderView(); + assertTrue(attributes.containsKey(CasViewConstants.MODEL_ATTRIBUTE_NAME_PRINCIPAL_CREDENTIAL)); + + final String encodedPsw = (String) attributes.get(CasViewConstants.MODEL_ATTRIBUTE_NAME_PRINCIPAL_CREDENTIAL); + final String password = decryptCredential(encodedPsw); + final UsernamePasswordCredential creds = TestUtils.getCredentialsWithSameUsernameAndPassword(); + assertEquals(password, creds.getPassword()); + } + + @Test + public void verifyProxyGrantingTicketAsAuthenticationAttributeCanDecrypt() throws Exception { + final Map attributes = renderView(); + assertTrue(attributes.containsKey(CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET)); + + final String encodedPgt = (String) attributes.get(CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET); + final String pgt = decryptCredential(encodedPgt); + assertNotNull(pgt); + } + + private String decryptCredential(final String cred) { + try { + final PrivateKeyFactoryBean factory = new PrivateKeyFactoryBean(); + factory.setAlgorithm("RSA"); + factory.setLocation(new ClassPathResource("RSA1024Private.p8")); + factory.setSingleton(false); + final PrivateKey privateKey = factory.getObject(); + + logger.debug("Initializing cipher based on [{}]", privateKey.getAlgorithm()); + final Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm()); + + logger.debug("Decoding value [{}]", cred); + final byte[] cred64 = CompressionUtils.decodeBase64ToByteArray(cred); + + logger.debug("Initializing decrypt-mode via private key [{}]", privateKey.getAlgorithm()); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + + final byte[] cipherData = cipher.doFinal(cred64); + return new String(cipherData); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/cas-server-webapp-support/src/test/java/org/jasig/cas/web/view/CasReloadableMessageBundleTests.java b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/view/CasReloadableMessageBundleTests.java new file mode 100644 index 000000000000..236e8ad3af2c --- /dev/null +++ b/cas-server-webapp-support/src/test/java/org/jasig/cas/web/view/CasReloadableMessageBundleTests.java @@ -0,0 +1,72 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas.web.view; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.springframework.context.NoSuchMessageException; + +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Misagh Moayyed + * @since 4.0.0 + */ +@RunWith(JUnit4.class) +public class CasReloadableMessageBundleTests { + private CasReloadableMessageBundle loader; + + @Before + public void setup() { + this.loader = new CasReloadableMessageBundle(); + this.loader.setFallbackToSystemLocale(false); + this.loader.setDefaultEncoding("UTF-8"); + this.loader.setBasenames("classpath:messages", "classpath:custom_messages"); + } + + @Test + public void verifyGetMessageFromRequestedBundle() { + final String msg = this.loader.getMessage("screen.blocked.header", null, Locale.FRENCH); + assertNotNull(msg); + } + + @Test + public void verifyGetMessageFromDefaultBundle() { + final String msg = this.loader.getMessage("screen.other.message", null, Locale.FRENCH); + assertEquals(msg, "another"); + } + + @Test + public void verifyUseCodeAsMessageItself() { + this.loader.setUseCodeAsDefaultMessage(true); + final String msg = this.loader.getMessage("does.not.exist", null, Locale.FRENCH); + assertEquals(msg, "does.not.exist"); + } + + @Test(expected=NoSuchMessageException.class) + public void verifyExpectExceptionWhenCodeNotFound() { + this.loader.setUseCodeAsDefaultMessage(false); + this.loader.getMessage("does.not.exist", null, Locale.FRENCH); + } +} diff --git a/cas-server-webapp-support/src/test/resources/RSA1024Private.key b/cas-server-webapp-support/src/test/resources/RSA1024Private.key new file mode 100644 index 000000000000..b31dd5153252 --- /dev/null +++ b/cas-server-webapp-support/src/test/resources/RSA1024Private.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDxsrlRM4XYSEzFROcEg0m82ZfexI2HhEKXG5C0l8xVE5xIlrP/ +UsmMchF4V7uT830tvDu3ZslhH+y6mEJEm7nrlg1qdQXqygPbWDrvznxlFBgR/S9O +u+3JnkGCJ9uLrkSlkTO6HODun+JL1BvZpYWLqZ83bI5MZYdJ4xgesmcjEQIDAQAB +AoGAFEe1yv1yvpoabvcAF13YwxLS7ms5oORVHg6/DpgqGf1iQKj8g3Dz3mf31Uwv +PhPRLhQ8QoBKZ27hUyrRbKZQbC1ViXXtYino2Qxf08q+uY+IChMYmEzK/q6FLhtg +hg4QrBJijGRQ81kwK2DSFpGuF+TLa4+ldMfpLu73RU+de00CQQD53V9Chym+w7g+ +D3AKhyygkxnBRIDb5m/V+iZubGMIgIrgOOdiI0VWDD20LNL7QUgx5LTvj44dEI/G +oAyVXJovAkEA96IEbOGJibyrukZOcC7u4lYbs6tVMHXNEonmtVrLa06Yhh8NcLRF +46w5MAUHJDUDggCadd8dANGnb6bAXr0GvwJARhz+XBa9ehBFpPSEBhBET5K3iWoF +lq8k9rBJFHdJmtsnHSAanYk0LZ8luWdSlLqO3aFFvGtV/4XkMmI65bakdQJBANwf +GO/wS+Iz5DLg7Disf4ySHm3HjyJUlMY17u6mlsv8QXh3ger9VGLdZLhav85fkY6u +Gp9MhOuFceC9yaJtROECQQCO4bSKwUX6sE1K0LD5UmorCpnp3oQrfDSvZQr3Aq6W +OIPpdHKEyC9kvFx6HUYn6Y8tprZvVOueK7jOxnyYYW7/ +-----END RSA PRIVATE KEY----- diff --git a/cas-server-webapp-support/src/test/resources/RSA1024Private.p8 b/cas-server-webapp-support/src/test/resources/RSA1024Private.p8 new file mode 100644 index 000000000000..7ed95131daa2 Binary files /dev/null and b/cas-server-webapp-support/src/test/resources/RSA1024Private.p8 differ diff --git a/cas-server-webapp-support/src/test/resources/RSA1024Public.key b/cas-server-webapp-support/src/test/resources/RSA1024Public.key new file mode 100644 index 000000000000..013c32894450 Binary files /dev/null and b/cas-server-webapp-support/src/test/resources/RSA1024Public.key differ diff --git a/cas-server-webapp-support/src/test/resources/RSA1024x509.pem b/cas-server-webapp-support/src/test/resources/RSA1024x509.pem new file mode 100644 index 000000000000..9aefd166279b --- /dev/null +++ b/cas-server-webapp-support/src/test/resources/RSA1024x509.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBDCCAm2gAwIBAgIJAOUmhgYijifpMA0GCSqGSIb3DQEBBQUAMGAxCzAJBgNV +BAYTAlVTMQswCQYDVQQIEwJBWjEQMA4GA1UEBxMHR2lsYmVydDEPMA0GA1UEChMG +QXBlcmVvMQwwCgYDVQQLEwNDQVMxEzARBgNVBAMTCmFwZXJlby5vcmcwHhcNMTUw +MjEzMTEyMTQ0WhcNNDIwNzAxMTEyMTQ0WjBgMQswCQYDVQQGEwJVUzELMAkGA1UE +CBMCQVoxEDAOBgNVBAcTB0dpbGJlcnQxDzANBgNVBAoTBkFwZXJlbzEMMAoGA1UE +CxMDQ0FTMRMwEQYDVQQDEwphcGVyZW8ub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDxsrlRM4XYSEzFROcEg0m82ZfexI2HhEKXG5C0l8xVE5xIlrP/UsmM +chF4V7uT830tvDu3ZslhH+y6mEJEm7nrlg1qdQXqygPbWDrvznxlFBgR/S9Ou+3J +nkGCJ9uLrkSlkTO6HODun+JL1BvZpYWLqZ83bI5MZYdJ4xgesmcjEQIDAQABo4HF +MIHCMB0GA1UdDgQWBBQcFWeT9G1tK8jiTLovaVMJ36W+xjCBkgYDVR0jBIGKMIGH +gBQcFWeT9G1tK8jiTLovaVMJ36W+xqFkpGIwYDELMAkGA1UEBhMCVVMxCzAJBgNV +BAgTAkFaMRAwDgYDVQQHEwdHaWxiZXJ0MQ8wDQYDVQQKEwZBcGVyZW8xDDAKBgNV +BAsTA0NBUzETMBEGA1UEAxMKYXBlcmVvLm9yZ4IJAOUmhgYijifpMAwGA1UdEwQF +MAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAND4+cmOEJmaF+O9IfnDb7UF+Pg0Om0p3 +tJJ6xnv4LyZzI9IdvNLuxoWsk7tOts6BBiYz/T86X0/oZ8NnwwAdljkCmqP3XlPF +CqrwhjQHetutxtz0SSkkRQvMXKZ650+ufNJcy0F0klVdxL1cWSX/y1Y0FpJ9atQK +hR89pmNbJ3E= +-----END CERTIFICATE----- diff --git a/cas-server-webapp-support/src/test/resources/WEB-INF/cas.properties b/cas-server-webapp-support/src/test/resources/WEB-INF/cas.properties new file mode 100644 index 000000000000..3b3cc930c53f --- /dev/null +++ b/cas-server-webapp-support/src/test/resources/WEB-INF/cas.properties @@ -0,0 +1,79 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +cas.loginUrl=http://localhost:8080/cas/login +# IP address or CIDR subnet allowed to access the /status URI of CAS that exposes health check information +cas.securityContext.status.allowedSubnet=127.0.0.1 + + +cas.themeResolver.defaultThemeName=cas-theme-default +cas.viewResolver.defaultViewsPathPrefix=/WEB-INF/view/jsp/default/ui/ + +## +# Unique CAS node name +# host.name is used to generate unique Service Ticket IDs and SAMLArtifacts. This is usually set to the specific +# hostname of the machine running the CAS node, but it could be any label so long as it is unique in the cluster. +host.name=cas01.example.org + +## +# Database flavors for Hibernate +# +# One of these is needed if you are storing Services or Tickets in an RDBMS via JPA. +# +# database.hibernate.dialect=org.hibernate.dialect.OracleDialect +# database.hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect +# database.hibernate.dialect=org.hibernate.dialect.HSQLDialect + +## +# Single Sign-On Session Timeouts +# Defaults sourced from WEB-INF/spring-configuration/ticketExpirationPolices.xml +# +# Maximum session timeout - TGT will expire in maxTimeToLiveInSeconds regardless of usage +# tgt.maxTimeToLiveInSeconds=28800 +# +# Idle session timeout - TGT will expire sooner than maxTimeToLiveInSeconds if no further requests +# for STs occur within timeToKillInSeconds +# tgt.timeToKillInSeconds=7200 + +## +# Service Ticket Timeout +# Default sourced from WEB-INF/spring-configuration/ticketExpirationPolices.xml +# +# Service Ticket timeout - typically kept short as a control against replay attacks, default is 10s. You'll want to +# increase this timeout if you are manually testing service ticket creation/validation via tamperdata or similar tools +# st.timeToKillInSeconds=10 + +## +# Single Logout Out Callbacks +# Default sourced from WEB-INF/spring-configuration/argumentExtractorsConfiguration.xml +# +# To turn off all back channel SLO requests set slo.disabled to true +# slo.callbacks.disabled=false + +## +# Log4j +# Default sourced from WEB-INF/spring-configuration/log4jConfiguration.xml: +# +# It is often time helpful to externalize log4j.xml to a system path to preserve settings between upgrades. +# e.g. log4j.config.location=/etc/cas/log4j.xml +# log4j.config.location=classpath:log4j.xml +# +# log4j refresh interval in millis +# log4j.refresh.interval=60000 + diff --git a/cas-server-webapp-support/src/test/resources/applicationContext.xml b/cas-server-webapp-support/src/test/resources/applicationContext.xml new file mode 100644 index 000000000000..90c084d5dca1 --- /dev/null +++ b/cas-server-webapp-support/src/test/resources/applicationContext.xml @@ -0,0 +1,51 @@ + + + + + Configuration for the default TicketRegistry which stores the tickets in-memory and cleans them out as specified intervals. + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp-support/src/test/resources/inMemoryThrottledSubmissionContext.xml b/cas-server-webapp-support/src/test/resources/inMemoryThrottledSubmissionContext.xml new file mode 100644 index 000000000000..0fba8efb2b18 --- /dev/null +++ b/cas-server-webapp-support/src/test/resources/inMemoryThrottledSubmissionContext.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp-support/src/test/resources/inspektrThrottledSubmissionContext.xml b/cas-server-webapp-support/src/test/resources/inspektrThrottledSubmissionContext.xml new file mode 100644 index 000000000000..218c6160cdd8 --- /dev/null +++ b/cas-server-webapp-support/src/test/resources/inspektrThrottledSubmissionContext.xml @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp-support/src/test/resources/jpaTestApplicationContext.xml b/cas-server-webapp-support/src/test/resources/jpaTestApplicationContext.xml new file mode 100644 index 000000000000..d7b4c8e324e2 --- /dev/null +++ b/cas-server-webapp-support/src/test/resources/jpaTestApplicationContext.xml @@ -0,0 +1,101 @@ + + + + + + + + + + org.hsqldb.jdbcDriver + sa + + jdbc:hsqldb:mem:cas-ticket-registry + org.hibernate.dialect.HSQLDialect + 1 + + + + + + + + org.jasig.cas.services + org.jasig.cas.ticket + + + + + + ${database.dialect} + update + ${database.batchSize} + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp-support/src/test/resources/log4j2.xml b/cas-server-webapp-support/src/test/resources/log4j2.xml new file mode 100644 index 000000000000..814dd80f3638 --- /dev/null +++ b/cas-server-webapp-support/src/test/resources/log4j2.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp-support/src/test/resources/messages.properties b/cas-server-webapp-support/src/test/resources/messages.properties new file mode 100644 index 000000000000..03ab241d5e68 --- /dev/null +++ b/cas-server-webapp-support/src/test/resources/messages.properties @@ -0,0 +1,21 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.blocked.header=Access Denied +screen.other.message=another diff --git a/cas-server-webapp-support/src/test/resources/messages_fr.properties b/cas-server-webapp-support/src/test/resources/messages_fr.properties new file mode 100644 index 000000000000..39cdbd88f4f1 --- /dev/null +++ b/cas-server-webapp-support/src/test/resources/messages_fr.properties @@ -0,0 +1,20 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.blocked.header=Accès non autorisé diff --git a/cas-server-webapp/.gitignore b/cas-server-webapp/.gitignore new file mode 100644 index 000000000000..ea8c4bf7f35f --- /dev/null +++ b/cas-server-webapp/.gitignore @@ -0,0 +1 @@ +/target diff --git a/cas-server-webapp/NOTICE b/cas-server-webapp/NOTICE new file mode 100644 index 000000000000..2fccf2e35dd5 --- /dev/null +++ b/cas-server-webapp/NOTICE @@ -0,0 +1,132 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + Annotations for Metrics under Apache License 2.0 + AntLR Parser Generator under BSD License + AOP alliance under Public Domain + Apache Commons Codec under Apache License, Version 2.0 + Apache Commons Lang under The Apache Software License, Version 2.0 + Apache Commons Logging under The Apache Software License, Version 2.0 + Apache HttpClient under Apache License, Version 2.0 + Apache HttpCore under Apache License, Version 2.0 + Apache Log4j API under The Apache Software License, Version 2.0 + Apache Log4j Commons Logging Bridge under The Apache Software License, Version 2.0 + Apache Log4j Core under The Apache Software License, Version 2.0 + Apache Log4j SLF4J Binding under The Apache Software License, Version 2.0 + Apache Log4j Web under The Apache Software License, Version 2.0 + Apereo CAS Core under Apache 2 + Apereo CAS Generic Support under Apache 2 + Apereo CAS Web Application under Apache 2 + Apereo CAS Web Application support under Apache 2 + AspectJ runtime under Eclipse Public License - v 1.0 + AspectJ weaver under Eclipse Public License - v 1.0 + Bean Validation API under The Apache Software License, Version 2.0 + Bouncy Castle Provider under Bouncy Castle Licence + c3p0:JDBC DataSources/Resource Pools under GNU LESSER GENERAL PUBLIC LICENSE + CAS server security filter under Apache 2 + CDI APIs under Apache License, Version 2.0 + ClassMate under The Apache Software License, Version 2.0 + Commons Collections under The Apache Software License, Version 2.0 + Commons IO under The Apache Software License, Version 2.0 + Commons JEXL under The Apache Software License, Version 2.0 + Core Hibernate O/RM functionality under GNU Lesser General Public License + Cryptacular Library under Apache 2 or GNU Lesser General Public License + dom4j under BSD License + Expression Language 2.2 Implementation under CDDL + GPLv2 with classpath exception + Expression Language 3.0 API under CDDL + GPLv2 with classpath exception + fastinfoset under Apache License, Version 2.0 + FindBugs-Annotations under GNU Lesser Public License + Guava: Google Core Libraries for Java under The Apache Software License, Version 2.0 + Hamcrest Core under New BSD License + Hibernate Commons Annotations under GNU Lesser General Public License + Hibernate Validator Engine under Apache License, Version 2.0 + Inspektr - Auditing API under Apache 2.0 License + Inspektr - Common API under Apache 2.0 License + Inspektr - Error Logging under Apache 2.0 License + Inspektr - Spring Framework Support under Apache 2.0 License + Interceptors 1.1 API under lgpl + istack common utility code runtime under CDDL 1.1 or GPL2 w/ CPE + Jackson Integration for Metrics under Apache License 2.0 + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Java Annotation Indexer under AL 2.0 + Java Architecture for XML Binding under CDDL 1.1 or GPL2 w/ CPE + Java Persistence API, Version 2.1 under Eclipse Public License (EPL), Version 1.0 or Eclipse Distribution License (EDL), Version 1.0 + Java Servlet API under CDDL + GPLv2 with classpath exception + Java Transaction API under Common Development and Distribution License or GNU General Public License, Version 2 with the Classpath Exception + Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.0 + javax.inject under The Apache Software License, Version 2.0 + JAXB CORE under CDDL 1.1 or GPL2 w/ CPE + JAXB Reference Implementation under CDDL 1.1 or GPL2 w/ CPE + JBoss Logging 3 under Apache License, version 2.0 + JCL 1.1.1 implemented over SLF4J under MIT License + jersey-core under CDDL 1.1 or GPL2 w/ CPE + jersey-server under CDDL 1.1 or GPL2 w/ CPE + jersey-servlet under CDDL 1.1 or GPL2 w/ CPE + jersey-spring under CDDL 1.1 or GPL2 w/ CPE + Joda-Time under Apache 2 + jose4j under The Apache Software License, Version 2.0 + JSR-250 Common Annotations for the JavaTM Platform under COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + JSR107 API and SPI under JSR-000107 JCACHE 2.9 Public Review - Updated Specification + License + JSR107 Cache RI under The Apache Software License, Version 2.0 + jsr173_api under Commons Development and Distribution License, Version 1.0 + jsr311-api under CDDL License + jstl under Commons Development and Distribution License, Version 1.0 + JUL to SLF4J bridge under MIT License + JUnit under Eclipse Public License 1.0 + JVM Integration for Metrics under Apache License 2.0 + Metrics Core under Apache License 2.0 + Metrics Health Checks under Apache License 2.0 + Metrics Spring Integration under Apache License 2.0 + Metrics Utility Servlets under Apache License 2.0 + Mockito under The MIT License + Objenesis under Apache 2 + OGNL - Object Graph Navigation Library under Apache License, Version 2.0 + Person Directory API under Apache License Version 2.0 + Person Directory Implementations under Apache License Version 2.0 + quartz under The Apache Software License, Version 2.0 + Reflections under WTFPL or The New BSD License + SLF4J API Module under MIT License + Spring AOP under The Apache Software License, Version 2.0 + Spring Beans under The Apache Software License, Version 2.0 + Spring Binding under The Apache Software License, Version 2.0 + Spring Context under The Apache Software License, Version 2.0 + Spring Context Support under The Apache Software License, Version 2.0 + Spring Core under The Apache Software License, Version 2.0 + Spring Expression Language (SpEL) under The Apache Software License, Version 2.0 + Spring JDBC under The Apache Software License, Version 2.0 + Spring JS under The Apache Software License, Version 2.0 + Spring JS Resources under The Apache Software License, Version 2.0 + Spring Object/Relational Mapping under The Apache Software License, Version 2.0 + Spring TestContext Framework under The Apache Software License, Version 2.0 + Spring Transaction under The Apache Software License, Version 2.0 + Spring Web under The Apache Software License, Version 2.0 + Spring Web Flow under The Apache Software License, Version 2.0 + Spring Web MVC under The Apache Software License, Version 2.0 + Spring Webflow Client Repository under Apache 2 + spring-security-config under The Apache Software License, Version 2.0 + spring-security-core under The Apache Software License, Version 2.0 + spring-security-web under The Apache Software License, Version 2.0 + standard under Apache License, Version 2.0 + Stax2 API under The BSD License + Streaming API for XML under Sun Binary Code License + Woodstox under The Apache Software License, Version 2.0 + XML Commons External Components XML APIs under The Apache Software License, Version 2.0 or The SAX License or The W3C License + diff --git a/cas-server-webapp/pom.xml b/cas-server-webapp/pom.xml new file mode 100644 index 000000000000..0a6b38010aa9 --- /dev/null +++ b/cas-server-webapp/pom.xml @@ -0,0 +1,110 @@ + + + + + org.jasig.cas + cas-server + 4.1.0-SNAPSHOT + + 4.0.0 + cas-server-webapp + war + Apereo CAS Web Application + + + org.jasig.cas + cas-server-webapp-support + ${project.version} + compile + + + org.springframework + spring-expression + runtime + + + javax.servlet + jstl + jar + runtime + + + taglibs + standard + 1.1.2 + jar + runtime + + + org.jasig.cas + cas-server-security-filter + runtime + + + com.ryantenney.metrics + metrics-spring + + + io.dropwizard.metrics + metrics-jvm + + + io.dropwizard.metrics + metrics-servlets + + + + + + + org.apache.maven.plugins + maven-war-plugin + + cas + + + ${basedir}/src/main/webapp/WEB-INF + true + WEB-INF + + **/web.xml + + + + + + + org.eclipse.jetty + jetty-maven-plugin + ${maven-jetty-plugin.version} + + + /cas + + + + + + + + ${project.parent.basedir} + + + diff --git a/cas-server-webapp/src/main/resources/apereo.properties b/cas-server-webapp/src/main/resources/apereo.properties new file mode 100644 index 000000000000..f3bbdca531dc --- /dev/null +++ b/cas-server-webapp/src/main/resources/apereo.properties @@ -0,0 +1,21 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +standard.custom.css.file=/themes/apereo/css/cas.css +cas.javascript.file=/themes/apereo/js/cas.js diff --git a/cas-server-webapp/src/main/resources/cas-theme-default.properties b/cas-server-webapp/src/main/resources/cas-theme-default.properties new file mode 100644 index 000000000000..efe19bca6cb7 --- /dev/null +++ b/cas-server-webapp/src/main/resources/cas-theme-default.properties @@ -0,0 +1,21 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +standard.custom.css.file=/css/cas.css +cas.javascript.file=/js/cas.js diff --git a/cas-server-webapp/src/main/resources/cas_views.properties b/cas-server-webapp/src/main/resources/cas_views.properties new file mode 100644 index 000000000000..901a006b6e94 --- /dev/null +++ b/cas-server-webapp/src/main/resources/cas_views.properties @@ -0,0 +1,26 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# A placeholder for view definitions that are to be defined +# in the format of: +# viewName.(class)=org.jasig.cas.web.view.ViewClassName +# This file is exclusively reserved for custom views that +# would be put into a CAS overlay by deployers and removes +# the need to have to overlay the entire xml view definition file. + diff --git a/cas-server-webapp/src/main/resources/log4j2.xml b/cas-server-webapp/src/main/resources/log4j2.xml new file mode 100644 index 000000000000..0fe200255d80 --- /dev/null +++ b/cas-server-webapp/src/main/resources/log4j2.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/resources/messages.properties b/cas-server-webapp/src/main/resources/messages.properties new file mode 100644 index 000000000000..369401078f5e --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages.properties @@ -0,0 +1,126 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=Congratulations on bringing CAS online! To learn how to authenticate, please review the default authentication handler configuration. +screen.welcome.security=For security reasons, please Log Out and Exit your web browser when you are done accessing services that require authentication! +screen.welcome.instructions=Enter your Username and Password +screen.welcome.label.netid=Username: +screen.welcome.label.netid.accesskey=u +screen.welcome.label.password=Password: +screen.welcome.label.password.accesskey=p +screen.welcome.label.publicstation=I am at a public workstation. +screen.welcome.label.warn=Warn me before logging me into other sites. +screen.welcome.label.warn.accesskey=w +screen.welcome.button.login=LOGIN +screen.welcome.button.clear=CLEAR + +screen.cookies.disabled.title=Browser cookies disabled +screen.cookies.disabled.message=Your browser does not accept cookies. Single Sign On WILL NOT WORK. + +screen.aup.button.accept=ACCEPT +screen.aup.button.cancel=CANCEL + +screen.nonsecure.title=Non-secure Connection +screen.nonsecure.message=You are currently accessing CAS over a non-secure connection. Single Sign On WILL NOT WORK. In order to have single sign on work, you MUST log in over HTTPS. + +logo.title=go to Apereo home page +copyright=Copyright © 2005–2015 Apereo, Inc. All rights reserved. +screen.capslock.on = CAPSLOCK key is turned on! + +# Remember-Me Authentication +screen.rememberme.checkbox.title=Remember Me + +# Blocked Errors Page +screen.blocked.header=Access Denied +screen.blocked.message=You've entered the wrong password for the user too many times. You've been throttled. +AbstractAccessDecisionManager.accessDenied=You are not authorized to access this resource. Contact your CAS administrator for more info. + +#Confirmation Screen Messages +screen.confirmation.message=Click here to go to the application. + +#Generic Success Screen Messages +screen.success.header=Log In Successful +screen.success.success=You, {0}, have successfully logged into the Central Authentication Service. +screen.success.security=When you are finished, for security reasons, please Log Out and Exit your web browser. + +#Logout Screen Messages +screen.logout.header=Logout successful +screen.logout.success=You have successfully logged out of the Central Authentication Service. +screen.logout.security=For security reasons, exit your web browser. +screen.logout.redirect=The service from which you arrived has supplied a link you may follow by clicking here. + +screen.service.sso.error.header=Re-Authentication Required to Access this Service +screen.service.sso.error.message=You attempted to access a service that requires authentication without re-authenticating. Please try authenticating again. +screen.service.required.message=You attempted authentication without specifying the target application. Please re-examine the request and try again. + +error.invalid.loginticket=You cannot attempt to re-submit a form that has been submitted already. +username.required=Username is a required field. +password.required=Password is a required field. + +# Authentication failure messages +authenticationFailure.AccountDisabledException=This account has been disabled. +authenticationFailure.AccountLockedException=This account has been locked. +authenticationFailure.CredentialExpiredException=Your password has expired. +authenticationFailure.InvalidLoginLocationException=You cannot login from this workstation. +authenticationFailure.InvalidLoginTimeException=Your account is forbidden to login at this time. +authenticationFailure.AccountNotFoundException=Invalid credentials. +authenticationFailure.FailedLoginException=Invalid credentials. +authenticationFailure.UNKNOWN=Invalid credentials. + +INVALID_REQUEST_PROXY=The request is incorrectly formatted. Ensure all required parameters are properly encoded and included. +INVALID_TICKET_SPEC=Ticket failed validation specification. Possible errors could include attempting to validate a Proxy Ticket via a Service Ticket validator, or not complying with the renew true request. +INVALID_REQUEST='service' and 'ticket' parameters are both required +INVALID_TICKET=Ticket ''{0}'' not recognized +INVALID_SERVICE=Ticket ''{0}'' does not match supplied service. The original service was ''{1}'' and the supplied service was ''{2}''. +INVALID_PROXY_CALLBACK=The supplied proxy callback url ''{0}'' could not be authenticated. +UNAUTHORIZED_SERVICE_PROXY=The supplied service ''{0}'' is not authorized to use CAS proxy authentication. + +screen.service.error.header=Application Not Authorized to Use CAS +service.not.authorized.missing.attr=You are not authorized to access the application as your account \ +is missing privileges required by the CAS server to authenticate into this service. Please notify your support desk. +screen.service.error.message=The application you attempted to authenticate to is not authorized to use CAS. +screen.service.empty.error.message=The services registry of CAS is empty and has no service definitions. \ +Applications that wish to authenticate with CAS must explicitly be defined in the services registry. + +# Password policy +password.expiration.warning=Your password expires in {0} day(s). Please change your password now. +password.expiration.loginsRemaining=You have {0} login(s) remaining before you MUST change your password. +screen.accountdisabled.heading=This account has been disabled. +screen.accountdisabled.message=Please contact the system administrator to regain access. +screen.accountlocked.heading=This account has been locked. +screen.accountlocked.message=Please contact the system administrator to regain access. +screen.expiredpass.heading=Your password has expired. +screen.expiredpass.message=Please change your password. +screen.mustchangepass.heading=You must change your password. +screen.mustchangepass.message=Please change your password. +screen.badhours.heading=Your account is forbidden to login at this time. +screen.badhours.message=Please try again later. +screen.badworkstation.heading=You cannot login from this workstation. +screen.badworkstation.message=Please contact the system administrator to regain access. + +# OAuth +screen.oauth.confirm.header=Authorization +screen.oauth.confirm.message=Do you want to grant access to your complete profile to "{0}" ? +screen.oauth.confirm.allow=Allow + +# Unavailable +screen.unavailable.heading=CAS is Unavailable +screen.unavailable.message=There was an error trying to complete your request. Please notify your support desk or try again. diff --git a/cas-server-webapp/src/main/resources/messages_ar.properties b/cas-server-webapp/src/main/resources/messages_ar.properties new file mode 100644 index 000000000000..7d05563b3f41 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_ar.properties @@ -0,0 +1,71 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=\u062A\u0647\u0627\u0646\u064A\u0646\u0627 \u0639\u0644\u0649 \u062C\u0644\u0628 CAS \u0639\u0644\u0649 \u0627\u0644\u0627\u0646\u062A\u0631\u0646\u062A! +screen.welcome.security=\u0644\u0623\u0633\u0628\u0627\u0628 \u0623\u0645\u0646\u064A\u0629\u060C \u0627\u0644\u0631\u062C\u0627\u0621 \u062A\u0633\u062C\u064A\u0644 \u0627\u0644\u062E\u0631\u0648\u062C \u0648\u062E\u0631\u0648\u062C \u0645\u062A\u0635\u0641\u062D \u0627\u0644\u0648\u064A\u0628 \u0627\u0644\u062E\u0627\u0635 \u0628\u0643 \u0628\u0639\u062F \u0627\u0644\u062D\u0635\u0648\u0644 \u0639\u0644\u0649 \u0627\u0644\u062E\u062F\u0645\u0627\u062A \u0627\u0644\u062A\u064A \u062A\u062A\u0637\u0644\u0628 \u0627\u0644\u0645\u0635\u0627\u062F\u0642\u0629! +screen.welcome.instructions=\u0623\u062F\u062E\u0644 \u0627\u0633\u0645 \u0627\u0644\u0645\u0633\u062A\u062E\u062F\u0645 \u0648\u0643\u0644\u0645\u0629 \u0627\u0644\u0633\u0631 +screen.welcome.label.netid=\u0627\u0633\u0645 \u0627\u0644\u0645\u0633\u062A\u062E\u062F\u0645 +screen.welcome.label.netid.accesskey= +screen.welcome.label.password=\u0643\u0644\u0645\u0629 \u0627\u0644\u0633\u0631 +screen.welcome.label.password.accesskey= +screen.welcome.label.warn=\u062A\u062D\u0630\u0631\u0646\u064A \u0642\u0628\u0644 \u062A\u0633\u062C\u064A\u0644\u064A \u0641\u064A \u0627\u0644\u0645\u0648\u0627\u0642\u0639 \u0627\u0644\u0623\u062E\u0631\u0649. +screen.welcome.label.warn.accesskey= +screen.welcome.button.login=\u062F\u062E\u0648\u0644 +screen.welcome.button.clear=\u0627\u0644\u063A\u0627 + +logo.title=\u0627\u0644\u0630\u0647\u0627\u0628 \u0625\u0644\u0649 \u0627\u0644\u0635\u0641\u062D\u0629 Apereo +copyright=\u062D\u0642 \u0627\u0644\u0646\u0634\u0631 © 2005 - 2015 Apereo, Inc. \u062C\u0645\u064A\u0639 \u0627\u0644\u062D\u0642\u0648\u0642 \u0645\u062D\u0641\u0648\u0638\u0629. + +# Blocked Errors Page +screen.blocked.header=\u0627\u0644\u0648\u0635\u0648\u0644 \u0645\u0631\u0641\u0648\u0636 +screen.blocked.message=\u0644\u0642\u062F \u0642\u0645\u062A \u0628\u0625\u062F\u062E\u0627\u0644 \u0643\u0644\u0645\u0629 \u0645\u0631\u0648\u0631 \u062E\u0627\u0637\u0626\u0629 \u0644\u0644\u0645\u0633\u062A\u062E\u062F\u0645 \u0645\u0631\u0627\u062A \u0643\u062B\u064A\u0631\u0629 \u062C\u062F\u0627. \u0644\u0642\u062F \u0643\u0646\u062A \u0645\u062E\u0646\u0648\u0642. + +#Confirmation Screen Messages +screen.confirmation.message=\u0627\u0636\u063A\u0637 \u0647\u0646\u0627 {0} \u0644\u0644\u0630\u0647\u0627\u0628 \u0625\u0644\u0649 \u0627\u0644\u062A\u0637\u0628\u064A\u0642 + +#Generic Success Screen Messages +screen.success.header=\u062A\u0633\u062C\u064A\u0644 \u0646\u0627\u062C\u062D +screen.success.success=\u0644\u0642\u062F \u0642\u0645\u062A \u0628\u062A\u0633\u062C\u064A\u0644 \u0627\u0644\u062F\u062E\u0648\u0644 \u0628\u0646\u062C\u0627\u062D \u0644\u0644\u062F\u062E\u0648\u0644 \u0625\u0644\u0649 \u0645\u0631\u0643\u0632 \u0627\u0644\u0645\u0635\u0627\u062F\u0642\u0629 +screen.success.security=\u0644\u0623\u0633\u0628\u0627\u0628 \u0623\u0645\u0646\u064A\u0629\u060C \u0627\u0644\u0631\u062C\u0627\u0621 \u062A\u0633\u062C\u064A\u0644 \u0627\u0644\u062E\u0631\u0648\u062C \u0648 \u0627\u0644\u062E\u0631\u0648\u062C \u0645\u0646 \u0645\u062A\u0635\u0641\u062D \u0627\u0644\u0648\u064A\u0628 \u0627\u0644\u062E\u0627\u0635 \u0628\u0643 \u0628\u0639\u062F \u0627\u0644\u062D\u0635\u0648\u0644 \u0639\u0644\u0649 \u0627\u0644\u062E\u062F\u0645\u0627\u062A \u0627\u0644\u062A\u064A \u062A\u062A\u0637\u0644\u0628 \u0627\u0644\u0645\u0635\u0627\u062F\u0642\u0629! + +#Logout Screen Messages +screen.logout.header=\u062E\u0631\u0648\u062C \u0646\u0627\u062C\u062D +screen.logout.success=\u0644\u0642\u062F \u0642\u0645\u062A \u0628\u062A\u0633\u062C\u064A\u0644 \u0627\u0644\u062E\u0631\u0648\u062C \u0628\u0646\u062C\u0627\u062D \u0644\u0644\u062E\u0631\u0648\u062C \u0645\u0646 \u0645\u0631\u0643\u0632 \u0627\u0644\u0645\u0635\u0627\u062F\u0642\u0629 +screen.logout.security=\u0644\u0623\u0633\u0628\u0627\u0628 \u0623\u0645\u0646\u064A\u0629\u060C \u064A\u062C\u0628 \u0627\u0644\u062E\u0631\u0648\u062C \u0645\u0646 \u0645\u062A\u0635\u0641\u062D \u0627\u0644\u0648\u064A\u0628 \u0627\u0644\u062E\u0627\u0635 \u0628\u0643 +screen.logout.redirect=\u0627\u0644\u0645\u0631\u0643\u0632 \u0627\u0644\u0630\u064A \u0648\u0635\u0644\u062A \u0645\u0646\u0647 \u0642\u062F \u0632\u0648\u062F \u0648\u0635\u0644 \u0627\u0631\u062A\u0628\u0627\u0637 \u0627\u062A\u0628\u0639 \u0628\u0627\u0644\u0636\u063A\u0637 \u0647\u0646\u0627 + +screen.service.sso.error.header= \u0644\u0644\u0648\u0635\u0648\u0644 \u0625\u0644\u0649 \u0647\u0630\u0627 \u0627\u0644\u0645\u0631\u0643\u0632 \u064A\u062C\u0628 \u0625\u0639\u0627\u062F\u0629 \u0627\u0644\u0645\u0635\u0627\u062F\u0642\u0629 +screen.service.sso.error.message=\u0644\u0642\u062F \u062D\u0627\u0648\u0644\u062A \u0627\u0644\u0648\u0635\u0648\u0644 \u0625\u0644\u0649 \u062E\u062F\u0645\u0629 \u064A\u062A\u0637\u0644\u0628 \u0645\u0635\u0627\u062F\u0642\u0629 \u0645\u0646 \u062F\u0648\u0646 \u0645\u0635\u0627\u062F\u0642\u0629 \u0645\u0646 \u062C\u062F\u064A\u062F.\u0627\u0644\u0631\u062C\u0627\u0621 \u062D\u0627\u0648\u0644 \u0644\u0645\u0635\u0627\u062F\u0642\u0629 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649 {0} + +error.invalid.loginticket=\u0644\u0627 \u064A\u0645\u0643\u0646\u0643 \u0645\u062D\u0627\u0648\u0644\u0629 \u0625\u0639\u0627\u062F\u0629 \u0627\u0644\u062A\u0642\u062F\u064A\u0645 \u0644\u0644\u0646\u0645\u0648\u0630\u062C \u0627\u0644\u0630\u064A \u062A\u0645 \u062A\u0642\u062F\u064A\u0645\u0647 \u0628\u0627\u0644\u0641\u0639\u0644 +required.username=\u0627\u0633\u0645 \u0627\u0644\u0645\u0633\u062A\u062E\u062F\u0645 \u0647\u0648 \u0627\u0644\u062D\u0642\u0644 \u0627\u0644\u0645\u0637\u0644\u0648\u0628 +required.password=\u0643\u0644\u0645\u0629 \u0627\u0644\u0633\u0631 \u0647\u064A \u0627\u0644\u062D\u0642\u0644 \u0627\u0644\u0645\u0637\u0644\u0648\u0628 +error.authentication.credentials.bad= \u0623\u0648\u0631\u0627\u0642 \u0627\u0644\u0627\u0639\u062A\u0645\u0627\u062F \u0627\u0644\u062A\u064A \u0642\u062F\u0645\u062A\u0647\u0627 \u0644\u0627 \u064A\u0645\u0643\u0646 \u062A\u062D\u062F\u064A\u0647 \u0644\u0644\u0645\u0635\u0627\u062F\u0642\u0629 \u0639\u0644\u064A\u0647 +error.authentication.credentials.unsupported= \u0623\u0648\u0631\u0627\u0642 \u0627\u0644\u0627\u0639\u062A\u0645\u0627\u062F \u0627\u0644\u062A\u064A \u0642\u062F\u0645\u062A\u0647\u0627 \u063A\u064A\u0631 \u0645\u0639\u062A\u0645\u062F\u0629 \u0628\u0648\u0627\u0633\u0637\u0629 + +INVALID_REQUEST_PROXY=pgt \u0648 targetService \u0645\u0639\u0644\u0645\u0627\u062A \u06A9\u0644\u0627\u0647\u0645\u0627 \u0645\u0637\u0644\u0648\u0628 +INVALID_TICKET_SPEC=\u0641\u0634\u0644 \u0627\u0644\u062A\u062D\u0642\u0642 \u0645\u0646 \u0635\u062D\u0629 \u0645\u0648\u0627\u0635\u0641\u0627\u062A \u0627\u0644\u062A\u0630\u0627\u0643\u0631. \u064A\u0645\u0643\u0646 \u0623\u0646 \u062A\u062A\u0636\u0645\u0646 \u0623\u062E\u0637\u0627\u0621 \u0645\u062D\u062A\u0645\u0644\u0629 \u062A\u062D\u0627\u0648\u0644 \u0627\u0644\u062A\u062D\u0642\u0642 \u0645\u0646 \u0635\u062D\u0629 \u0627\u0644\u062A\u0630\u0627\u0643\u0631 \u0639\u0646 \u0637\u0631\u064A\u0642 \u0648\u0643\u064A\u0644 \u0645\u062F\u0642\u0642 \u062A\u0630\u0643\u0631\u0629 \u0627\u0644\u062E\u062F\u0645\u0629\u060C \u0623\u0648 \u0644\u0645 \u064A\u0645\u062A\u062B\u0644 \u0644\u0637\u0644\u0628 \u062A\u062C\u062F\u064A\u062F \u062D\u0642\u064A\u0642\u064A +INVALID_REQUEST=service \u0648 ticket \u0645\u0639\u0644\u0645\u0627\u062A \u06A9\u0644\u0627\u0647\u0645\u0627 \u0645\u0637\u0644\u0648\u0628 +INVALID_TICKET=\u062A\u0630\u0643\u0631\u0629 {0} \u0644\u0627 \u064A\u0639\u062A\u0631\u0641 +INVALID_SERVICE=\u062A\u0630\u0643\u0631\u0629 {0} \u0644\u0627 \u064A\u062A\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u062E\u062F\u0645\u0629 \u0627\u0644\u0645\u0642\u062F\u0645\u0629. \u0643\u0627\u0646\u062A \u0627\u0644\u062E\u062F\u0645\u0629 \u0627\u0644\u0623\u0635\u0644\u064A\u0629 {1} \u0648\u06A9\u0627\u0646\u062A \u0627\u0644\u062E\u062F\u0645\u0629 \u0627\u0644\u0645\u0642\u062F\u0645\u0629 {2{ + +screen.service.error.header=\u0627\u0644\u062A\u0637\u0628\u064A\u0642 \u0644\u0627 \u064A\u0633\u0645\u062D \u0627\u0633\u062A\u062E\u062F\u0627\u0645 CAS +screen.service.error.message=\u0627\u0644\u062E\u062F\u0645\u0629 \u0627\u0644\u062A\u064A \u062A\u0637\u0644\u0628\u064A\u0646\u0647\u0627 \u063A\u0631 \u0645\u0633\u0645\u0648\u062D\u0629 \u0628\u0647\u0627 \u0644\u062F\u0649 \u0627\u0633\u062A\u0639\u0645\u0627\u0644 CAS diff --git a/cas-server-webapp/src/main/resources/messages_ca.properties b/cas-server-webapp/src/main/resources/messages_ca.properties new file mode 100644 index 000000000000..38f579fddf93 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_ca.properties @@ -0,0 +1,113 @@ +#Author: Evili del Rio i Silvan and Alex Henrie + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=Felicitats per engegar el CAS correctament! Per a aprendre com autenticar, si us plau, repasseu la configuració del gestor d'autenticació per defecte. +screen.welcome.security=Per raons de seguretat, si us plau, tanqueu la sessió i el vostre navegador web quan hàgiu acabat d'accedir als serveis que requereixen autenticació. +screen.welcome.instructions=Introduïu el vostre nom d'usuari i contrasenya. +screen.welcome.label.netid=Nom d'usuari: +screen.welcome.label.netid.accesskey=u +screen.welcome.label.password=Contrasenya: +screen.welcome.label.password.accesskey=c +screen.welcome.label.warn=Aviseu-me abans d'obrir sessió en altres llocs. +screen.welcome.label.warn.accesskey=a +screen.welcome.button.login=INICIA SESSIÓ +screen.welcome.button.clear=NETEJA + +logo.title=vés a la pàgina principal de l'Apereo +copyright=Copyright © 2005–2015 Apereo, Inc. Es reserven tots els drets. +screen.capslock.on = La tecla BLOQ MAJ està activada! + +# Blocked Errors Page +screen.blocked.header=Accés denegat +screen.blocked.message=Heu introduïda una contrasenya equivocada per al usuari massa vegades. Se us ha restringit. + +#Confirmation Screen Messages +screen.confirmation.message=Feu clic aquí per a anar a l'aplicació. + +#Generic Success Screen Messages +screen.success.header=Inici de sessió reeixit +screen.success.success=Vós, {0}, heu iniciat amb èxit la sessió al Servei Central d''Autenticació (CAS). +screen.success.security=Per raons de seguretat, si us plau, tanqueu la sessió i el vostre navegador web quan hàgiu terminat. + +#Logout Screen Messages +screen.logout.header=Tancament de sessió reeixit +screen.logout.success=Heu tancat amb èxit la sessió al Servei Central d'Autenticació (CAS). +screen.logout.security=Per raons de seguretat, tanqueu el vostre navegador web. +screen.logout.redirect=El servei des del qual heu arribat ha proporcionat un enllaç que podeu seguir per fer clic aquí. + +screen.service.sso.error.header=Cal reautenticar per a accedir a aquest servei +screen.service.sso.error.message=Heu intentat accedir a un servei que requereix autenticació sense reautenticar. Si us plau, intenteu autenticar de nou. + +error.invalid.loginticket=No podeu intentar reenviar un formulari que ja s'ha enviat. +required.username=El nom d'usuari és un camp obligatori. +required.password=La contrasenya és un camp obligatori. + +# Authentication failure messages +authenticationFailure.AccountDisabledException=S'ha deshabilitat aquest compte. +authenticationFailure.AccountLockedException=S'ha bloquejat aquest compte. +authenticationFailure.CredentialExpiredException=La vostra contrasenya ha caducada. +authenticationFailure.InvalidLoginLocationException=No podeu iniciar sessió des d'aquesta estació de treball. +authenticationFailure.InvalidLoginTimeException=Està prohibit iniciar sessió amb el vostre compte en aquest moment. +authenticationFailure.AccountNotFoundException=Les credencials són invàlides. +authenticationFailure.FailedLoginException=Les credencials són invàlides. +authenticationFailure.UNKNOWN=Les credencials són invàlides. + +INVALID_REQUEST_PROXY=calen ambdós dels paràmetres 'pgt' i 'targetService' +INVALID_TICKET_SPEC=El tiquet ha fallat l'especificació de validació. Els errors possibles poden incloure intentar validar un tiquet d'intermediari mitjançant un validador de tiquets de servei, o no complir amb la petició de renovació (renew true). +INVALID_REQUEST=calen ambdós dels paràmetres 'service' i 'ticket' +INVALID_TICKET=No s''ha reconegut el tiquet ''{0}'' +INVALID_SERVICE=El tiquet ''{0}'' no coincideix amb el servei proporcionat. El servei original era ''{1}'' i el servei proporcionat era ''{2}''. +INVALID_PROXY_CALLBACK=L''adreça de retrotrucada d''intermediari proveïda ''{0}'' no s''ha pogut autenticar. +UNAUTHORIZED_SERVICE_PROXY=El servei proporcionat ''{0}'' no està autoritzat a utilitzar l''autenticació intermediària del CAS. + +screen.service.error.header=Aplicació no autoritzada a utilitzar el CAS +service.not.authorized.missing.attr=No esteu autoritzat a accedir a l'aplicació perquè al vostre compte \ +li manquen els privilegis que el servidor CAS requereix per a autenticar a aquest servei. Si us plau, notifiqueu al vostre suport tècnic. +screen.service.error.message=L'aplicació a que heu intentat autenticar no està autoritzada a utilitzar el CAS. +screen.service.empty.error.message=El registre de serveis del CAS està buit i no té definicions de servei. \ +Les aplicacions que volen autenticar amb el CAS han de ser explícitament definides en el registre de serveis. + +# Password policy +password.expiration.warning=La vostra contrasenya caduca en {0} dies. Si us plau, canvieu la vostra contrasenya ara. +password.expiration.loginsRemaining=Teniu {0} inicis de sessió restant abans que HEU de canviar la vostra contrasenya. +screen.accountdisabled.heading=S'ha deshabilitat aquest compte. +screen.accountdisabled.message=Si us plau, contacteu a l'administrador de sistema per a recobrar accés. +screen.accountlocked.heading=S'ha bloquejat aquest compte. +screen.accountlocked.message=Si us plau, contacteu a l'administrador de sistema per a recobrar accés. +screen.expiredpass.heading=La vostra contrasenya ha caducada. +screen.expiredpass.message=Si us plau, canvieu la vostra contrasenya. +screen.mustchangepass.heading=Heu de canviar la vostra contrasenya. +screen.mustchangepass.message=Si us plau, canvieu la vostra contrasenya. +screen.badhours.heading=Està prohibit iniciar sessió amb el vostre compte en aquest moment. +screen.badhours.message=Si us plau, intenteu més tard. +screen.badworkstation.heading=No podeu iniciar sessió des d'aquesta estació de treball. +screen.badworkstation.message=Si us plau, contacteu a l'administrador de sistema per a recobrar accés. + +# OAuth +screen.oauth.confirm.header=Autorització +screen.oauth.confirm.message=Voleu concedir accés al vostre perfil complet a "{0}"? +screen.oauth.confirm.allow=Permet + +# Unavailable +screen.unavailable.heading=El CAS no està disponible +screen.unavailable.message=Ha hagut un error al intentar complir amb la vostra petició. Si us plau, notifiqueu al vostre suport tècnic o intenteu de nou. diff --git a/cas-server-webapp/src/main/resources/messages_cs.properties b/cas-server-webapp/src/main/resources/messages_cs.properties new file mode 100644 index 000000000000..cc3e6a61b84b --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_cs.properties @@ -0,0 +1,103 @@ +#Generated by ResourceBundle Editor (http://eclipse-rbe.sourceforge.net) +#Welcome Screen Messages + +INVALID_PROXY_CALLBACK = Poskytnut\u00E9 URL proxy callbacku ''{0}'' nelze autentifikovat. + +INVALID_REQUEST = Parametry 'service' a 'ticket' jsou povinn\u00E9 + +INVALID_REQUEST_PROXY = Parametry 'pgt' a 'targetService' jsou povinn\u00E9 + +INVALID_SERVICE = Ticket ''{0}'' nesouhlas\u00ED s poskytovanou slu\u017Ebou. P\u016Fvodn\u00ED slu\u017Eba byla ''{1}'', poskytnut\u00E1 je ''{2}''. + +INVALID_TICKET = Ticket ''{0}'' nebyl rozpozn\u00E1n + +INVALID_TICKET_SPEC = Ticket neporo\u0161el kontrolou validity. Mo\u017En\u00E9 chyby zahrnuj\u00ED pokus o ov\u011B\u0159en\u00ED Proxy Ticketu pomoc\u00ED ov\u011B\u0159en\u00ED Service Ticketu nebo nedodr\u017Een\u00ED po\u017Eadavku na renew. + +UNAUTHORIZED_SERVICE_PROXY = Poskytnut\u00E1 slu\u017Eba ''{0}'' nen\u00ED opr\u00E1vn\u011Bn\u00ED k pou\u017Eit\u00ED CAS proxy autentizace. + +authenticationFailure.AccountDisabledException = Tento \u00FA\u010Det byl zak\u00E1z\u00E1n. +authenticationFailure.AccountLockedException = Tento \u00FA\u010Det byl uzam\u010Den. +authenticationFailure.AccountNotFoundException = Nezn\u00E1m\u00E9 p\u0159ihla\u0161ovac\u00ED \u00FAdaje. +authenticationFailure.CredentialExpiredException = Va\u0161e heslo ji\u017E nen\u00ED platn\u00E9. +authenticationFailure.FailedLoginException = Neplatn\u00E9 p\u0159ihla\u0161ovac\u00ED \u00FAdaje. +authenticationFailure.InvalidLoginLocationException = Z tohoto po\u010D\u00EDta\u010De se nem\u016F\u017Eete p\u0159ihl\u00E1sit. +authenticationFailure.InvalidLoginTimeException = P\u0159ihl\u00E1\u0161en\u00ED v tento \u010Das pro V\u00E1\u0161 \u00FA\u010Det povoleno. +authenticationFailure.UNKNOWN = Neplatn\u00E9 p\u0159ihla\u0161ovac\u00ED \u00FAdaje. + +copyright = Copyright © 2005–2012 Apereo, Inc. V\u0161echna pr\u00E1va vyhrazena. + +error.invalid.loginticket = Nen\u00ED mo\u017En\u00E9 znovu odeslat formul\u00E1\u0159, kter\u00FD ji\u017E byl odesl\u00E1n. + +logo.title = j\u00EDt na str\u00E1nky Apereo + +password.expiration.loginsRemaining = Zb\u00FDv\u00E1 V\u00E1m {0} p\u0159ihl\u00E1\u0161en\u00ED, ne\u017E budete MUSET zm\u011Bnit sv\u00E9 heslo. +password.expiration.warning = Va\u0161e heslo vypr\u0161\u00ED za {0} dn\u00ED. Zm\u011B\u0148te pros\u00EDm ihned sv\u00E9 heslo. + +required.password = Heslo je povinn\u00FD \u00FAdaj. +required.username = U\u017Eivatelsk\u00E9 jm\u00E9no je povinn\u00FD \u00FAdaj. + +screen.accountdisabled.heading = Tento \u00FA\u010Det byl zak\u00E1z\u00E1n. +screen.accountdisabled.message = Pro obnoven\u00ED p\u0159\u00EDstupu kontaktujte pros\u00EDm sv\u00E9ho syst\u00E9mov\u00E9ho administr\u00E1tora. +screen.accountlocked.heading = Tento \u00FA\u010Det byl uzam\u010Den. +screen.accountlocked.message = Pro obnoven\u00ED p\u0159\u00EDstupu kontaktujte pros\u00EDm sv\u00E9ho syst\u00E9mov\u00E9ho administr\u00E1tora. +screen.badhours.heading = V\u00E1\u0161 \u00FA\u010Det nem\u00E1 povolen\u00ED k p\u0159ihl\u00E1\u0161en\u00ED v tomto \u010Dase. +screen.badhours.message = Zkuste to pros\u00EDm pozd\u011Bji. +screen.badworkstation.heading = Z tohoto po\u010D\u00EDta\u010De se nem\u016F\u017Eete p\u0159ihl\u00E1sit. +screen.badworkstation.message = Pro obnoven\u00ED p\u0159\u00EDstupu kontaktujte pros\u00EDm sv\u00E9ho syst\u00E9mov\u00E9ho administr\u00E1tora. +screen.blocked.header = P\u0159\u00EDstup odep\u0159en +screen.blocked.message = Zadal(a) jste \u0161patn\u00E9 heslo p\u0159\u00EDli\u0161 \u010Dasto. P\u0159\u00EDtsup byl do\u010Dasn\u011B zablokov\u00E1n. +#Confirmation Screen Messages +screen.confirmation.message = Pro p\u0159echod na web klikn\u011Bte zde. +screen.expiredpass.heading = Va\u0161e heslo ji\u017E n\u011Bn\u00ED platn\u00E9. +screen.expiredpass.message = Zm\u011B\u0148te pros\u00EDm sv\u00E9 heslo. +#Logout Screen Messages +screen.logout.header = \u00DAsp\u011B\u0161n\u00E9 odhl\u00E1\u0161en\u00ED +screen.logout.redirect = Web ze kter\u00E9ho jste sem p\u0159i\u0161li doporu\u010Dil odkaz, kam pokra\u010Dovat. +screen.logout.security = Z bezpe\u010Dnostn\u00EDch d\u016Fvod\u016F uzav\u0159ete v\u0161echna okna prohl\u00ED\u017Ee\u010De. +screen.logout.success = \u00DAsp\u011B\u0161n\u011B jste se odhl\u00E1sili od Centr\u00E1ln\u00ED Autentiza\u010Dn\u00ED Slu\u017Eby. +screen.mustchangepass.heading = Mus\u00EDte zm\u011Bnit sv\u00E9 heslo. +screen.mustchangepass.message = Zm\u011B\u0148te pros\u00EDm sv\u00E9 heslo. +screen.oauth.confirm.allow = Povolit +screen.oauth.confirm.header = Autorizace +screen.oauth.confirm.message = Chcete povolit p\u0159\u00EDstup ke sv\u00E9mu profilu pro "{0}"? +screen.service.empty.error.message = Registr slu\u017Eeb CASu je pr\u00E1zdn\u00FD a nem\u00E1 definovan\u00E9 \u017E\u00E1dn\u00E9 slu\u017Eby. Aplikace, kter\u00E9 chcete autentizovat pomoc\u00ED CASu mus\u00EDte explicitn\u011B uv\u00E9st v registru slu\u017Eeb. +#Service Error Messages +screen.service.error.header = Aplikace nen\u00ED autorizovan\u00E1 k pou\u017Eit\u00ED p\u0159ihl\u0161ov\u00E1n\u00ED pomoc\u00ED CASu. +screen.service.error.message = Aplikace ke kter\u00E9 se sna\u017E\u00EDte p\u0159ihl\u00E1sit nen\u00ED opr\u00E1vn\u011Bna k vyu\u017Eit\u00ED CASu. +screen.service.sso.error.header = Pro tuto slu\u017Ebu je po\u017Eadov\u00E1no op\u011Btovn\u00E9 p\u0159ihl\u00E1\u0161en\u00ED +screen.service.sso.error.message = Pokou\u0161\u00EDte se p\u0159istoupit ke slu\u017Eb\u011B, kter\u00E1 vy\u017Eaduje op\u011Btovn\u00E9 p\u0159ihl\u00E1\u0161en\u00ED. Zkuste se pros\u00EDm p\u0159ihl\u00E1sit znovu. +#Generic Success Screen Messages +screen.success.header = \u00DAsp\u011B\u0161n\u00E9 p\u0159ihl\u00E1\u0161en\u00ED +screen.success.security = Z bezpe\u010Dnostn\u00EDch d\u016Fvod\u016F se po ukon\u010Den\u00ED pr\u00E1ce odhla\u0161te a zav\u0159ete v\u0161echna okna prohl\u00ED\u017Ee\u010De! +screen.success.success = \u00DAsp\u011B\u0161n\u011B jste se p\u0159ihl\u00E1sili k Centr\u00E1ln\u00ED Autentika\u010Dn\u00ED Slu\u017Eb\u011B. +screen.unavailable.heading = CAS nen\u00ED dostupn\u00FD +screen.unavailable.message = P\u0159i zpracov\u00E1n\u00ED Va\u0161eho po\u017Eadavku do\u0161lo k chyb\u011B. Uv\u011Bdomte pros\u00EDm syst\u00E9movou podporu nebo to zkuste znovu. +screen.welcome.button.clear = VY\u010CISTIT +screen.welcome.button.login = P\u0158IHL\u00C1SIT +screen.welcome.instructions = Zadejte sv\u00E9 u\u017Eivatelsk\u00E9 jm\u00E9no a heslo +screen.welcome.label.netid = U\u017Eivatelsk\u00E9 jm\u00E9no +screen.welcome.label.netid.accesskey = u +screen.welcome.label.password = Heslo: +screen.welcome.label.password.accesskey = h +screen.welcome.label.warn = Upozornit p\u0159ed p\u0159ihl\u00E1\u0161en\u00ED k jin\u00E9 aplikaci. +screen.welcome.label.warn.accesskey = z +screen.welcome.security = Z bezpe\u010Dnostn\u00EDch d\u016Fvod\u016F se po ukon\u010Den\u00ED pr\u00E1ce odhla\u0161te a zav\u0159ete v\u0161echna okna prohl\u00ED\u017Ee\u010De! +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +screen.welcome.welcome = Gratulujeme, \u00FAsp\u011B\u0161n\u011B jste zprovoznili CAS! Pro zji\u0161t\u011Bn\u00ED, jak se p\u0159ihl\u00E1sit, prohl\u00E1dn\u011Bte si v\u00FDchoz\u00ED konfiguraci autentifika\u010Dn\u00EDho handleru. diff --git a/cas-server-webapp/src/main/resources/messages_de.properties b/cas-server-webapp/src/main/resources/messages_de.properties new file mode 100644 index 000000000000..6b3d5a715d21 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_de.properties @@ -0,0 +1,108 @@ +# Welcome Screen Messages GERMAN + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=Glückwunsch, Sie haben Ihr CAS System zum Laufen gebracht! Der voreingestellte Authentication Handler gewährt Einlass, wenn der Benutzername dem Passwort entspricht: Nur zu, probieren Sie es aus. +screen.welcome.security=Aus Sicherheitsgründen sollten Sie bei Verlassen der passwortgeschützten Bereiche sich explizit ausloggen und Ihren Webbrowser schließen! +screen.welcome.instructions=Bitte geben Sie Ihre Apereo NetID und Ihr Passwort ein. +screen.welcome.label.netid=NetID: +screen.welcome.label.netid.accesskey=n +screen.welcome.label.password=Passwort: +screen.welcome.label.password.accesskey=p +screen.welcome.label.warn=Ich möchte gewarnt werden, bevor ich mich in einen anderen Bereich einlogge. +screen.welcome.label.warn.accesskey=w +screen.welcome.button.login=ANMELDEN +screen.welcome.button.clear=LÖSCHEN + +logo.title=zur Apereo Seite wechseln +copyright=Copyright © 2005–2015 Apereo, Inc. Alle Rechte vorbehalten. + +# Blocked Errors Page +screen.blocked.header=Zugriff verweigert +screen.blocked.message=Das Kennwort f\u00FCr den Benutzer wurde zu oft falsch eingegeben. Der Zugriff wird gedrosselt. + +#Confirmation Screen Messages +screen.confirmation.message=Klicken Sie hier um zu der zuvor angeforderten Seite zurückzukehren. + +#Generic Success Screen Messages +screen.success.header=Anmeldung erfolgreich +screen.success.success=Sie haben sich erfolgreich am Central Authentication Service angemeldet. +screen.success.security=Aus Sicherheitsgründen sollten Sie bei Verlassen der passwortgeschützten Bereiche sich explizit ausloggen und Ihren Webbrowser schliessen! + +#Logout Screen Messages +screen.logout.header=Abmeldung erfolgreich +screen.logout.success=Sie haben sich erfolgreich vom Central Authentication Service abgemeldet. +screen.logout.security=Aus Sicherheitsgründen sollten Sie den Browser schliessen. +screen.logout.redirect=Der Service, von dem Sie herkommen, hat einen Link angegeben, den Sie verfolgen k\u00F6nnen, indem Sie hier klicken. + +screen.service.sso.error.header=Eine Neuanmeldung ist erforderlich, um auf den Service zuzugreifen. +screen.service.sso.error.message=Der Service, für den Sie versucht haben, sich zu authentifizieren, hat nicht das Recht, CAS zu benutzen. + +error.invalid.loginticket=Sie können kein Formular erneut abschicken, das bereits übertragen wurde. +required.username=Benutzername ist ein Pflichtfeld. +required.password=Passwort ist ein Pflichtfeld. + +# Authentication failure messages +authenticationFailure.AccountDisabledException=Dieses Konto wurde deaktiviert. +authenticationFailure.AccountLockedException=Dieses Konto wurde gesperrt. +authenticationFailure.CredentialExpiredException=Ihr Kennwort ist abgelaufen. +authenticationFailure.InvalidLoginLocationException=Sie k\u00F6nnen sich von dieser Workstation nicht anmelden. +authenticationFailure.InvalidLoginTimeException=Ihrem Konto ist es nicht gestattet sich zu diesem Zeitpunkt anzumelden. +authenticationFailure.AccountNotFoundException=Ung\u00FCltige Anmeldedaten. +authenticationFailure.FailedLoginException=Ung\u00FCltige Anmeldedaten. +authenticationFailure.UNKNOWN=Ung\u00FCltige Anmeldedaten. + +INVALID_REQUEST_PROXY='pgt' und 'targetService' Parameter werden beide benötigt +INVALID_TICKET_SPEC=Das Ticket entspricht nicht den Überprüfungsregeln. Ein möglicher Fehler könnte sein, dass versucht wurde, ein Proxy Ticket mit einem Service Ticket Validierer zu überprüfen, oder man sich nicht an den renew true Request gehalten hat. +INVALID_REQUEST='service' und 'ticket' Parameter werden beide benötigt +INVALID_TICKET=Ticket ''{0}'' wurde nicht anerkannt +INVALID_SERVICE=Ticket ''{0}'' passt nicht zum angegebenen Service. Der ursprüngliche Service war ''{1}'' und der übermittelte Service war ''{2}''. +INVALID_PROXY_CALLBACK=Die angegebene Proxy-Callback-Url ''{0}'' kann nicht authentifiziert werden. +UNAUTHORIZED_SERVICE_PROXY=Dem angegebenen Service ''{0}'' ist es nicht gestattet eine CAS Proxy Authentifizierung zu verwenden. + +screen.service.error.header=Applikation nicht berechtigt CAS zu verwenden +screen.service.error.message=Die Applikation, mit der eine Authentifkation versucht wurde, ist nicht berechtigt CAS zu verwenden. +screen.service.empty.error.message=Die Service-Registrierung des CAS Server ist leer und hat keine Service-Definitionen.\ +Applikationen, welche \u00FCber CAS authentifizieren m\u00F6chten, m\u00FCssen in der Services-Registrierung definiert werden. + +# Password policy +password.expiration.warning=Ihr Kennwort l\u00E4uft in {0} Tagen ab. Bitte \u00E4ndern Sie Ihr Kennwort. +password.expiration.loginsRemaining=Sie haben {0} anmeldungen \u00FCbrig, bevor Sie Ihr Kennwort \u00E4ndern m\u00FCssen. +screen.accountdisabled.heading=Dieses Konto wurde deaktiviert. +screen.accountdisabled.message=Bitte kontaktieren Sie Ihren System Administrator um wieder Zugriff zu erhalten. +screen.accountlocked.heading=Dieses Konto wurde gesperrt. +screen.accountlocked.message=Bitte kontaktieren Sie den Systemadministrator um wieder Zugang zu erlangen. +screen.expiredpass.heading=Ihr Kennwort ist abgelaufen. +screen.expiredpass.message=Bitte ändern Sie Ihr Kennwort. +screen.mustchangepass.heading=Sie müssen Ihr Kennwort ändern. +screen.mustchangepass.message=Bitte ändern Sie Ihr Kennwort. +screen.badhours.heading=Ihrem Konto ist es nicht gestattet sich zu diesem Zeitpunkt anzumelden. +screen.badhours.message=Bitte versuchen Sie es später noch einmal. +screen.badworkstation.heading=Sie können sich von dieser Workstation aus nicht anmelden. +screen.badworkstation.message=Bitte kontaktieren Sie Ihren System Administrator um Zugriff zu erhalten. + +# OAuth +screen.oauth.confirm.header=Authorisierung +screen.oauth.confirm.message=Wollen Sie "{0}" vollen Zugriff auf Ihr Profil gestatten? +screen.oauth.confirm.allow=Erlauben + +# Unavailable +screen.unavailable.heading=CAS ist nicht verf\u00FCgbar +screen.unavailable.message=Beim Verarbeiten Ihrer Anfrage ist ein Fehler aufgetreten. Bitte informieren Sie Ihren Support oder versuchen Sie es noch einmal. diff --git a/cas-server-webapp/src/main/resources/messages_es.properties b/cas-server-webapp/src/main/resources/messages_es.properties new file mode 100644 index 000000000000..3987bd9ec57f --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_es.properties @@ -0,0 +1,113 @@ +#Author: Joaquin Recio, Jose Luis Huertas, Juan Paulo Soto, and Alex Henrie + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=¡Felicidades por iniciar CAS correctamente! Para aprender cómo autenticar, por favor repase la configuración del gestor de configuración por defecto. +screen.welcome.security=Por razones de seguridad, ¡por favor cierre su sesión y su navegador web cuando haya terminado de acceder a los servicios que requieren autenticación! +screen.welcome.instructions=Introduzca su nombre de usuario y contraseña. +screen.welcome.label.netid=Nombre de usuario: +screen.welcome.label.netid.accesskey=u +screen.welcome.label.password=Contraseña: +screen.welcome.label.password.accesskey=c +screen.welcome.label.warn=Avisarme antes de abrir sesión en otros sitios. +screen.welcome.label.warn.accesskey=a +screen.welcome.button.login=INICIAR SESIÓN +screen.welcome.button.clear=LIMPIAR + +logo.title=ir a la página principal de Apereo +copyright=Copyright © 2005–2015 Apereo, Inc. Se reservan todos los derechos. +screen.capslock.on = ¡La tecla BLOQ MAYÚS está activada! + +# Blocked Errors Page +screen.blocked.header=Acceso denegado +screen.blocked.message=Ha introducido una contraseña equivocada para el usuario demasiadas veces. Se le ha restringido. + +#Confirmation Screen Messages +screen.confirmation.message=Haga clic aquí para ir a la aplicación. + +#Generic Success Screen Messages +screen.success.header=Inicio de sesión exitoso +screen.success.success=Usted, {0}, ha iniciado con éxito su sesión en el Servicio de Autenticación Central. +screen.success.security=Por razones de seguridad, por favor cierre su sesión y su navegador web cuando haya terminado de acceder a los servicios que requieren autenticación. + +#Logout Screen Messages +screen.logout.header=Cierre de sesión exitoso +screen.logout.success=Ha cerrado con éxito su sesión del Servicio de Autenticación Central. +screen.logout.security=Por razones de seguridad, cierre su navegador web. +screen.logout.redirect=El servicio desde el cual ha llegado ha proporcionado un enlace que puede seguir por hacer clic aquí. + +screen.service.sso.error.header=Reautenticación requerida para acceder a este servicio +screen.service.sso.error.message=Intentó acceder a un servicio que requiere autenticación sin reautenticar. Por favor intente autenticar de nuevo. + +error.invalid.loginticket=No puede intentar reenviar un formulario que ya se ha enviado. +required.username=El nombre de usuario es un campo requerido. +required.password=La contraseña es un campo requerido. + +# Authentication failure messages +authenticationFailure.AccountDisabledException=Se ha deshabilitado esta cuenta. +authenticationFailure.AccountLockedException=Se ha bloqueado esta cuenta. +authenticationFailure.CredentialExpiredException=Su contraseña ha caducado. +authenticationFailure.InvalidLoginLocationException=No puede iniciar sesión desde esta estación de trabajo. +authenticationFailure.InvalidLoginTimeException=Está prohibido iniciar sesión con su cuenta en este momento. +authenticationFailure.AccountNotFoundException=Credenciales inválidas. +authenticationFailure.FailedLoginException=Credenciales inválidas. +authenticationFailure.UNKNOWN=Credenciales inválidas. + +INVALID_REQUEST_PROXY=ambos de los parámetros 'pgt' y 'targetService' se requieren +INVALID_TICKET_SPEC=El tique falló la especificación de validación. Los errores posibles pueden incluir intentar validar un tique de proxy mediante un validador de tiques de servicio, o no cumplir con la petición de renovación (renew true). +INVALID_REQUEST=ambos de los parámetros 'service' y 'ticket' se requieren +INVALID_TICKET=No se ha reconocido el tique ''{0}'' +INVALID_SERVICE=El tique ''{0}'' no coincide con el servicio proporcionado. El servicio original era ''{1}'' y el servicio proporcionado era ''{2}''. +INVALID_PROXY_CALLBACK=La dirección web de retrollamada de proxy ''{0}'' no se pudo autenticar. +UNAUTHORIZED_SERVICE_PROXY=El servicio proporcionado ''{0}'' no está autorizado a usar la autenticación de proxy CAS. + +screen.service.error.header=Aplicación no autorizada a usar CAS +service.not.authorized.missing.attr=No está autorizado a accedir a la aplicación porque a su cuenta \ +le faltan privilegios que el servidor CAS requiere para autenticar a este servicio. Por favor, notifique a su soporte técnico. +screen.service.error.message=La aplicación que usted ha intentado autenticar no está autorizada a usar CAS. +screen.service.empty.error.message=El registro de servicios del CAS está vacío y no tiene definiciones de servicio. \ +Las aplicaciones que quieren autenticar con CAS deben ser explícitamente definidas en el registro de servicios. + +# Password policy +password.expiration.warning=Su contraseña caduca en {0} días. Por favor cambie su contraseña ahora. +password.expiration.loginsRemaining=Tiene {0} inicios de sesión restantes antes que DEBE cambiar su contraseña. +screen.accountdisabled.heading=Se ha deshabilitado esta cuenta. +screen.accountdisabled.message=Por favor contacte al administrador de sistema para recobrar acceso. +screen.accountlocked.heading=Se ha bloqueado esta cuenta. +screen.accountlocked.message=Por favor contacte al administrador de sistema para recobrar acceso. +screen.expiredpass.heading=Su contraseña ha caducado. +screen.expiredpass.message=Por favor cambie su contraseña. +screen.mustchangepass.heading=Debe cambiar su contraseña. +screen.mustchangepass.message=Por favor cambie su contraseña. +screen.badhours.heading=Está prohibido iniciar sesión con su cuenta en este momento. +screen.badhours.message=Por favor intente más tarde. +screen.badworkstation.heading=No puede iniciar sesión desde esta estación de trabajo. +screen.badworkstation.message=Por favor contacte al administrador de sistema para recobrar acceso. + +# OAuth +screen.oauth.confirm.header=Autorización +screen.oauth.confirm.message=¿Quiere conceder acceso a su perfil completo a "{0}"? +screen.oauth.confirm.allow=Permitir + +# Unavailable +screen.unavailable.heading=CAS no está disponible +screen.unavailable.message=Hubo un error al intentar cumplir con su petición. Por favor notifique a su soporte técnico o intente otra vez. diff --git a/cas-server-webapp/src/main/resources/messages_fa.properties b/cas-server-webapp/src/main/resources/messages_fa.properties new file mode 100644 index 000000000000..57399ef00345 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_fa.properties @@ -0,0 +1,71 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=\u0645\u0648\u0641\u0642 \u0634\u062F\u06CC\u062F CAS \u0631\u0627 \u0628\u0647 \u0635\u0648\u0631\u062A \u0622\u0646\u0644\u0627\u06CC\u0646 \u0628\u0627\u0631\u06AF\u0630\u0627\u0631\u06CC \u06A9\u0646\u06CC\u062F! \u0645\u062F\u06CC\u0631 \u062A\u0627\u06CC\u06CC\u062F \u067E\u06CC\u0634 \u0641\u0631\u0636\u060C \u062A\u0627\u06CC\u06CC\u062F \u0645\u06CC\u06A9\u0646\u062F \u0686\u0647 \u0632\u0645\u0627\u0646\u06CC \u0646\u0627\u0645 \u06A9\u0627\u0631\u0628\u0631\u06CC \u0648 \u0631\u0645\u0632 \u0648\u0631\u0648\u062F \u0647\u0645\u062E\u0648\u0627\u0646\u06CC \u062F\u0627\u0631\u0646\u062F: \u0645\u0631\u0627\u062D\u0644 \u0631\u0627 \u0627\u062F\u0627\u0645\u0647 \u062F\u0647\u06CC\u062F \u0648 \u0627\u0645\u062A\u062D\u0627\u0646 \u06A9\u0646\u06CC\u062F. +screen.welcome.security=\u0628\u0647 \u062F\u0644\u0627\u06CC\u0644 \u0627\u0645\u0646\u06CC\u062A\u06CC \u0632\u0645\u0627\u0646\u06CC \u06A9\u0647 \u062F\u06CC\u06AF\u0631 \u0646\u06CC\u0627\u0632\u06CC \u0628\u0647 \u062F\u0633\u062A\u06CC\u0627\u0628\u06CC \u0628\u0647 \u0633\u0631\u0648\u06CC\u0633\u0647\u0627\u06CC\u06CC \u06A9\u0647 \u0627\u062D\u062A\u06CC\u0627\u062C \u0628\u0647 \u062A\u0627\u06CC\u06CC\u062F \u062F\u0627\u0631\u0646\u062F \u0646\u062F\u0627\u0634\u062A\u06CC\u062F\u060C \u0627\u0632 \u067E\u0627\u06CC\u06AF\u0627\u0647 \u062E\u0627\u0631\u062C \u0634\u062F\u0647 \u0645\u0631\u0648\u0631\u06AF\u0631 \u062E\u0648\u062F \u0631\u0627 \u0628\u0628\u0646\u062F\u06CC\u062F! +screen.welcome.instructions=\u0646\u0627\u0645 \u06A9\u0627\u0631\u0628\u0631\u06CC \u0648 \u0631\u0645\u0632 \u0648\u0631\u0648\u062F \u0631\u0627 \u0648\u0627\u0631\u062F \u06A9\u0646\u06CC\u062F +screen.welcome.label.netid=\u0646\u0627\u0645 \u06A9\u0627\u0631\u0628\u0631\u06CC +screen.welcome.label.netid.accesskey= +screen.welcome.label.password=\u0631\u0645\u0632 \u0648\u0631\u0648\u062F +screen.welcome.label.password.accesskey= +screen.welcome.label.warn=\u0642\u0628\u0644 \u0627\u0632 \u0648\u0631\u0648\u062F \u0628\u0647 \u0633\u0627\u06CC\u062A\u0647\u0627\u06CC \u062F\u06CC\u06AF\u0631 \u0628\u0647 \u0645\u0646 \u0647\u0634\u062F\u0627\u0631 \u0628\u062F\u0647 +screen.welcome.label.warn.accesskey= +screen.welcome.button.login=\u0648\u0631\u0648\u062F +screen.welcome.button.clear=\u0627\u0646\u0635\u0631\u0627\u0641 + +logo.title=\u0628\u0647 \u0635\u0641\u062D\u0647 \u0627\u0635\u0644\u06CC Apereo \u0628\u0631\u0648 +copyright=\u062D\u0642 \u0646\u0634\u0631 © 2005 - 2015 Apereo, Inc. \u06A9\u0644\u06CC\u0647 \u062D\u0642\u0648\u0642 \u0645\u062D\u0641\u0648\u0638 \u0627\u0633\u062A + +# Blocked Errors Page +screen.blocked.header=\u062F\u0633\u062A\u0631\u0633\u06CC \u0645\u0645\u06A9\u0646 \u0646\u06CC\u0633\u062A +screen.blocked.message=\u0628\u0647 \u062F\u0641\u0639\u0627\u062A \u0628\u0631\u0627\u06CC \u0646\u0627\u0645 \u06A9\u0627\u0631\u0628\u0631\u06CC\u060C \u0631\u0645\u0632 \u0648\u0631\u0648\u062F \u0631\u0627 \u0627\u0634\u062A\u0628\u0627\u0647 \u0648\u0627\u0631\u062F \u06A9\u0631\u062F\u0647\u0627\u06CC\u062F. \u0627\u0632 \u0648\u0631\u0648\u062F \u0634\u0645\u0627 \u062C\u0644\u0648\u06AF\u06CC\u0631\u06CC \u0634\u062F\u0647 \u0627\u0633\u062A. + +#Confirmation Screen Messages +screen.confirmation.message=\u0628\u0631\u0627\u06CC \u0648\u0631\u0648\u062F \u0628\u0647 \u0628\u0631\u0646\u0627\u0645\u0647 \u0627\u06CC\u0646\u062C\u0627 \u0631\u0627 \u06A9\u0644\u06CC\u06A9 \u06A9\u0646\u06CC\u062F + +#Generic Success Screen Messages +screen.success.header=\u0648\u0631\u0648\u062F \u0645\u0648\u0641\u0642\u06CC\u062A \u0622\u0645\u06CC\u0632 \u0628\u0648\u062F +screen.success.success=\u0628\u0627 \u0645\u0648\u0641\u0642\u06CC\u062A \u0648\u0627\u0631\u062F \u067E\u0627\u06CC\u06AF\u0627\u0647 \u062A\u0627\u06CC\u06CC\u062F \u0645\u0631\u06A9\u0632\u06CC CAS \u0634\u062F\u06CC\u062F +screen.success.security=\u0628\u0647 \u062F\u0644\u0627\u06CC\u0644 \u0627\u0645\u0646\u06CC\u062A\u06CC \u0632\u0645\u0627\u0646\u06CC \u06A9\u0647 \u062F\u06CC\u06AF\u0631 \u0646\u06CC\u0627\u0632\u06CC \u0628\u0647 \u062F\u0633\u062A\u06CC\u0627\u0628\u06CC \u0628\u0647 \u0633\u0631\u0648\u06CC\u0633 \u0647\u0627\u06CC\u06CC \u06A9\u0647 \u0627\u062D\u062A\u06CC\u0627\u062C \u0628\u0647 \u062A\u0627\u06CC\u06CC\u062F \u062F\u0627\u0631\u0646\u062F \u0646\u062F\u0627\u0634\u062A\u06CC\u062F\u060C \u0627\u0632 \u067E\u0627\u06CC\u06AF\u0627\u0647 \u062E\u0627\u0631\u062C \u0634\u062F\u0647 \u0645\u0631\u0648\u0631\u06AF\u0631 \u062E\u0648\u062F \u0631\u0627 \u0628\u0628\u0646\u062F\u06CC\u062F! + +#Logout Screen Messages +screen.logout.header=\u062E\u0631\u0648\u062C \u0627\u0632 \u067E\u0627\u06CC\u06AF\u0627\u0647 \u0645\u0648\u0641\u0642\u06CC\u062A \u0622\u0645\u06CC\u0632 \u0628\u0648\u062F +screen.logout.success=\u0628\u0627 \u0645\u0648\u0641\u0642\u06CC\u062A \u0627\u0632 \u067E\u0627\u06CC\u06AF\u0627\u0647 \u062A\u0627\u06CC\u06CC\u062F \u0645\u0631\u06A9\u0632\u06CC CAS \u062E\u0627\u0631\u062C \u0634\u062F\u06CC\u062F +screen.logout.security=\u0628\u0631\u0627\u06CC \u062D\u0641\u0638 \u0627\u0645\u0646\u06CC\u062A \u0627\u0632 \u0645\u0631\u0648\u0631\u06AF\u0631 \u062E\u0648\u062F \u062E\u0627\u0631\u062C \u0634\u0648\u06CC\u062F +screen.logout.redirect=\u0633\u0631\u0648\u06CC\u0633\u06CC \u0627\u0631\u062C\u0627\u0639 \u062F\u0647\u0646\u062F\u0647 \u0634\u0645\u0627 \u0627\u06CC\u0646 \u0644\u06CC\u0646\u06A9 \u0631\u0627 \u062A\u0648\u0644\u06CC\u062F \u06A9\u0631\u062F\u0647 \u0627\u0633\u062A. \u0628\u0631\u0627\u06CC \u0627\u062F\u0627\u0645\u0647 \u0644\u06CC\u0646\u06A9 \u0631\u0627 \u06A9\u0644\u06CC\u06A9 \u06A9\u0646\u06CC\u062F. + +screen.service.sso.error.header=\u0628\u0631\u0627\u06CC \u062F\u0633\u062A\u0631\u0633\u06CC \u0628\u0647 \u0627\u06CC\u0646 \u0633\u0631\u0648\u06CC\u0633 \u0646\u06CC\u0627\u0632 \u0628\u0647 \u062A\u0627\u06CC\u06CC\u062F \u062F\u0648\u0628\u0627\u0631\u0647 \u062F\u0627\u0631\u06CC\u062F +screen.service.sso.error.message=\u0633\u0639\u06CC \u062F\u0627\u0634\u062A\u06CC\u062F \u0628\u062F\u0648\u0646 \u062A\u0627\u06CC\u06CC\u062F \u062F\u0648\u0628\u0627\u0631\u0647\u060C \u0628\u0647 \u0633\u0631\u0648\u06CC\u0633\u06CC \u062F\u0633\u062A\u0631\u0633\u06CC \u067E\u06CC\u062F\u0627 \u06A9\u0646\u06CC\u062F \u06A9\u0647 \u0646\u06CC\u0627\u0632 \u0628\u0647 \u062A\u0627\u06CC\u06CC\u062F \u062F\u0627\u0631\u062F. \u0644\u0637\u0641\u0627\u064B \u0628\u0639\u062F \u0627\u0632 \u062A\u0627\u06CC\u06CC\u062F \u062F\u0648\u0628\u0627\u0631\u0647 \u0627\u0645\u062A\u062D\u0627\u0646 \u06A9\u0646\u06CC\u062F + +error.invalid.loginticket=\u0646\u0645\u06CC\u062A\u0648\u0627\u0646\u06CC\u062F \u0641\u0631\u0645\u06CC \u06A9\u0647 \u0627\u0631\u0633\u0627\u0644 \u0634\u062F\u0647 \u0631\u0627 \u062F\u0648\u0628\u0627\u0631\u0647 \u0627\u0631\u0633\u0627\u0644 \u06A9\u0646\u06CC\u062F +required.username=\u0648\u0627\u0631\u062F \u06A9\u0631\u062F\u0646 \u0646\u0627\u0645 \u06A9\u0627\u0631\u0628\u0631\u06CC \u0627\u0644\u0632\u0627\u0645\u06CC \u0627\u0633\u062A +required.password=\u0648\u0627\u0631\u062F \u06A9\u0631\u062F\u0646 \u0631\u0645\u0632 \u0648\u0631\u0648\u062F \u0627\u0644\u0632\u0627\u0645\u06CC \u0627\u0633\u062A +error.authentication.credentials.bad=\u0646\u0627\u0645 \u06A9\u0627\u0631\u0628\u0631\u06CC \u0648 \u0631\u0645\u0632 \u0648\u0631\u0648\u062F \u0635\u062D\u06CC\u062D \u0646\u0645\u06CC\u0628\u0627\u0634\u062F +error.authentication.credentials.unsupported=>CAS \u0627\u06CC\u0646 \u0646\u0627\u0645 \u06A9\u0627\u0631\u0628\u0631\u06CC \u0648 \u0631\u0645\u0632 \u0648\u0631\u0648\u062F \u0631\u0627 \u067E\u0634\u062A\u06CC\u0627\u0646\u06CC \u0646\u0645\u06CC\u06A9\u0646\u062F + +INVALID_REQUEST_PROXY=\u067E\u0627\u0631\u0627\u0645\u062A\u0631\u0647\u0627\u06CC pgt \u0648 targetService \u0647\u0631 \u062F\u0648 \u0627\u0644\u0632\u0627\u0645\u06CC \u0647\u0633\u062A\u0646\u062F +INVALID_TICKET_SPEC=\u0634\u0646\u0627\u0633\u0647 \u0645\u0648\u0631\u062F \u062A\u0627\u06CC\u06CC\u062F \u0642\u0631\u0627\u0631 \u0646\u06AF\u0631\u0641\u062A. \u062E\u0637\u0627\u0647\u0627\u06CC \u0645\u0645\u06A9\u0646 \u0645\u06CC\u062A\u0648\u0627\u0646\u062F \u0634\u0627\u0645\u0644 \u0633\u0639\u06CC \u062F\u0631 \u0645\u0648\u0631\u062F \u062A\u0627\u06CC\u06CC\u062F \u0642\u0631\u0627\u0631 \u062F\u0627\u062F\u0646 \u0634\u0646\u0627\u0633\u0647-\u06CC \u067E\u0631\u0627\u06A9\u0633\u06CC \u0627\u0632 \u0637\u0631\u06CC\u0642 \u0633\u06CC\u0633\u062A\u0645 \u062A\u0627\u06CC\u06CC\u062F \u06A9\u0646\u0646\u062F\u0647\u06CC \u0634\u0646\u0627\u0633\u0647\u06CC \u0633\u0631\u0648\u06CC\u0633 \u06CC\u0627 \u0647\u0645\u062E\u0648\u0627\u0646\u06CC \u0646\u062F\u0627\u0634\u062A\u0646 \u0628\u0627 \u062F\u0631\u062E\u0648\u0627\u0633\u062A \u062A\u062C\u062F\u06CC\u062F \u0634\u062F\u0647 \u0628\u0627\u0634\u062F. +INVALID_REQUEST=\u067E\u0627\u0631\u0627\u0645\u062A\u0631\u0647\u0627\u06CC service\u0648 ticket \u0647\u0631 \u062F\u0648 \u0627\u0644\u0632\u0627\u0645\u06CC \u0647\u0633\u062A\u0646\u062F +INVALID_TICKET=\u0634\u0646\u0627\u0633\u0647 {0} \u0634\u0646\u0627\u0633\u0627\u06CC\u06CC \u0646\u0634\u062F +INVALID_SERVICE=\u0634\u0646\u0627\u0633\u0647 {0} \u0628\u0627 \u0633\u0631\u0648\u06CC\u0633 \u0639\u0631\u0636\u0647 \u0634\u062F\u0647 \u0647\u0645\u062E\u0648\u0627\u0646\u06CC \u0646\u062F\u0627\u0631\u062F. \u0633\u0631\u0648\u06CC\u0633 \u0627\u0635\u0644\u06CC{1} \u0648 \u0633\u0631\u0648\u06CC\u0633 \u0639\u0631\u0636\u0647 \u0634\u062F\u0647{2} \u0628\u0648\u062F\u0647 \u0627\u0633\u062A. + +screen.service.error.header=\u0628\u0631\u0646\u0627\u0645\u0647 \u0628\u0631\u0627\u06CC \u0627\u0633\u062A\u0641\u0627\u062F\u0647 \u0627\u0632 CAS \u062A\u0627\u06CC\u06CC\u062F \u0646\u0634\u062F\u0647 \u0627\u0633\u062A +screen.service.error.message=\u0628\u0631\u0646\u0627\u0645\u0647\u0627\u06CC \u06A9\u0647 \u0633\u0639\u06CC \u062F\u0631 \u062A\u0627\u06CC\u06CC\u062F \u0622\u0646 \u062F\u0627\u0634\u062A\u06CC\u062F\u060C \u0628\u0631\u0627\u06CC \u0627\u0633\u062A\u0641\u0627\u062F\u0647 \u0627\u0632 CAS \u0645\u0639\u062A\u0628\u0631 \u0646\u06CC\u0633\u062A. diff --git a/cas-server-webapp/src/main/resources/messages_fr.properties b/cas-server-webapp/src/main/resources/messages_fr.properties new file mode 100644 index 000000000000..60e43b6282c2 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_fr.properties @@ -0,0 +1,126 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=Félicitations, votre serveur est en ligne ! Pour savoir comment vous authentifier, merci de regarder la méthode d'authentification définie par défaut. +screen.welcome.security=Pour des raisons de sécurité, veuillez vous déconnecter et fermer votre navigateur lorsque vous avez fini d'accéder aux services authentifiés. +screen.welcome.instructions=Entrez votre identifiant et votre mot de passe. +screen.welcome.label.netid=Identifiant: +screen.welcome.label.netid.accesskey=i +screen.welcome.label.password=Mot de passe: +screen.welcome.label.password.accesskey=m +screen.welcome.label.publicstation=Je suis sur un ordinateur public. +screen.welcome.label.warn=Prévenez-moi avant d'accéder à d'autres services. +screen.welcome.label.warn.accesskey=p +screen.welcome.button.login=SE CONNECTER +screen.welcome.button.clear=EFFACER + +screen.cookies.disabled.title=Cookies navigateur désactivés +screen.cookies.disabled.message=Votre navigateur ne supporte pas les cookies. L'authentification centralisée NE FONCTIONNERA PAS. + +screen.aup.button.accept=ACCEPTER +screen.aup.button.cancel=ANNULER + +screen.nonsecure.title=Connexion non sécurisée +screen.nonsecure.message=Vous accédez actuellement au serveur CAS via une connexion non sécurisée. L'authentification centralisée NE FONCTIONNERA PAS. Pour faire fonctionner l'authentification centralisée, vous devez vous authentifier en HTTPS. + +logo.title=allez à la page d'accueil Apereo +copyright=Copyright © 2005–2015 Apereo, Inc. Tous droits réservés. +screen.capslock.on = la touche Verr Maj est activée ! + +# Remember-Me Authentication +screen.rememberme.checkbox.title=Se souvenir de moi + +# Blocked Errors Page +screen.blocked.header=Accès non autorisé +screen.blocked.message=Vous avez saisi un mauvais mot de passe trop de fois de suite. Vous avez été rejeté. +AbstractAccessDecisionManager.accessDenied=Vous n'êtes pas autorisé à accéder à cette ressource. Contactez votre administrateur CAS pour plus d'informations. + +#Confirmation Screen Messages +screen.confirmation.message=Cliquez ici pour accéder au service. + +#Generic Success Screen Messages +screen.success.header=Connexion réussie +screen.success.success=Vous vous êtes authentifié(e) auprès du Service Central d'Authentification. +screen.success.security=Pour des raisons de sécurité, veuillez vous déconnecter et fermer votre navigateur lorsque vous avez fini d'accéder aux services authentifiés. + +#Logout Screen Messages +screen.logout.header=Déconnexion réussie +screen.logout.success=Vous vous êtes déconnecté(e) du Service Central d'Authentification. +screen.logout.security=Pour des raisons de sécurité, veuillez fermer votre navigateur. +screen.logout.redirect=Le service duquel vous arrivez a fourni un lien que vous pouvez suivre en cliquant ici. + +screen.service.sso.error.header=Une nouvelle authentification est requise pour accéder à ce service. +screen.service.sso.error.message=Vous avez tenté d'accéder à un service qui requiert une nouvelle authentification sans vous authentifier à nouveau. Veuillez vous authentifier de nouveau. +screen.service.required.message=Vous avez tenté de vous authentifier sans préciser d'application cible. Merci de vérifier votre requête et de recommencer. + +error.invalid.loginticket=Vous ne pouvez pas re-soumettre un formulaire d'autentification qui a déjà été soumis. +required.username=Vous devez entrer votre identifiant. +required.password=Vous devez entrer votre mot de passe. + +# Authentication failure messages +authenticationFailure.AccountDisabledException=Votre compte a été désactivé. +authenticationFailure.AccountLockedException=Votre compte est bloqué. +authenticationFailure.CredentialExpiredException=Votre mot de passe a expiré. +authenticationFailure.InvalidLoginLocationException=Vous ne pouvez pas vous authentifier depuis cet ordinateur. +authenticationFailure.InvalidLoginTimeException=Vous ne pouvez pas vous authentifier pendant cette période. +authenticationFailure.AccountNotFoundException=Mauvais identifiant / mot de passe. +authenticationFailure.FailedLoginException=Mauvais identifiant / mot de passe. +authenticationFailure.UNKNOWN=Mauvais identifiant / mot de passe. + +INVALID_REQUEST_PROXY=Les paramètres 'pgt' et 'targetService' sont tous deux nécessaires +INVALID_TICKET_SPEC=La validation du ticket est impossible. Les raisons possibles peuvent être la validation d'un Proxy Ticket sur une URL de validation de Service Ticket, ou une mauvaise requête de type renew. +INVALID_REQUEST=Les paramètres 'service' et 'ticket' sont tous deux nécessaires +INVALID_TICKET=Le ticket ''{0}'' est inconnu +INVALID_SERVICE=Le ticket ''{0}'' ne correspond pas au service demandé. Le service original était ''{1}'' et le service demandé était ''{2}''. +INVALID_PROXY_CALLBACK=L''url de rappel du proxy ''{0}'' n''a pu être authentifiée. +UNAUTHORIZED_SERVICE_PROXY=Le service utilisé ''{0}'' n''est pas autorisé à utiliser l''authentification proxy CAS. + +screen.service.error.header=Application non autorisée à utiliser CAS +service.not.authorized.missing.attr=Vous n'êtes pas autorisé à accéder à cette application car votre compte \ +n'a pas les privilèges requis par le seveur CAS pour accéder à ce service. Merci de contacter votre support. +screen.service.error.message=L'application pour laquelle vous avez tenté de vous authentifier n'est pas autorisée à utiliser CAS. +screen.service.empty.error.message=Aucun service CAS n'a été défini. \ +Les applications qui veulent s'authentifier sur CAS doivent explicitement s'enregistrer dans la base des services. + +# Password policy +password.expiration.warning=Votre mot de passe expire dans {0} jour(s). Merci de changer votre mot de passe maintenant. +password.expiration.loginsRemaining=Il vous reste {0} authentification(s) avant de DEVOIR changer votre mot de passe. +screen.accountdisabled.heading=Ce compte a été désactivé. +screen.accountdisabled.message=Merci de contacter votre administrateur système pour récupérer votre accès. +screen.accountlocked.heading=Votre compte a été bloqué. +screen.accountlocked.message=Merci de contacter votre administrateur pour le débloquer. +screen.expiredpass.heading=Votre mot de passe a expiré. +screen.expiredpass.message=Merci de changer votre mot de passe. +screen.mustchangepass.heading=Vous devez changer votre mot de passe. +screen.mustchangepass.message=Merci de changer votre mot de passe. +screen.badhours.heading=Vous ne pouvez pas vous authentifier durant cette plage horaire. +screen.badhours.message=Merci de réessayer plus tard. +screen.badworkstation.heading=Vous ne pouvez pas vous authentifier depuis cet ordinateur. +screen.badworkstation.message=Merci de contacter votre administrateur système pour récupérer votre accès. + +# OAuth +screen.oauth.confirm.header=Autorisation +screen.oauth.confirm.message=Voulez-vous donner l'accès de votre profil complet à "{0}" ? +screen.oauth.confirm.allow=Autoriser + +# Unavailable +screen.unavailable.heading=CAS est indisponible +screen.unavailable.message=Une erreur s'est produite lors du traitement de votre requête. Merci de contacter votre support ou de réessayer. diff --git a/cas-server-webapp/src/main/resources/messages_hr.properties b/cas-server-webapp/src/main/resources/messages_hr.properties new file mode 100644 index 000000000000..348fc062f128 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_hr.properties @@ -0,0 +1,70 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# @author Nebojsa Topolscak +# @author Jasmina Plavac +# University Computing Center - Zagreb, Croatia +# @since 3.1.1 + +#Welcome Screen Messages + +screen.welcome.welcome=\u010cestitamo na uspje\u0161noj instalaciji CAS-a! Inicijalni autentikacijski mehanizam obavlja uspje\u0161nu autentikaciju uno\u0161enjem korisni\u010dkog imena i zaporke iste vrijednosti. Isprobajte! +screen.welcome.security=Iz sigurnosnih razloga molimo vas da se odjavite i zatvorite web preglednik nakon \u0161to zavr\u0161ite s radom u aplikacijama koje zahtijevaju autentikaciju. +screen.welcome.instructions=Unesite korisni\u010dko ime i zaporku. +screen.welcome.label.netid=Korisni\u010dko ime: +screen.welcome.label.netid.accesskey=k +screen.welcome.label.password=Zaporka: +screen.welcome.label.password.accesskey=z +screen.welcome.label.warn=Upozori me prije prijave u druge aplikacije. +screen.welcome.label.warn.accesskey=u +screen.welcome.button.login=PRIJAVA +screen.welcome.button.clear=PONI\u0160TI + +#Confirmation Screen Messages +screen.confirmation.message=Pritisnite ovdje za ulaz u aplikaciju. + +#Generic Success Screen Messages +screen.success.header=Uspje\u0161na prijava +screen.success.success=Uspje\u0161no ste se prijavili u Centralni autentikacijski servis. +screen.success.security=Iz sigurnosnih razloga molimo vas da se odjavite i zatvorite web preglednik nakon \u0161to zavr\u0161ite s radom u aplikacijama koje zahtijevaju autentikaciju. + +#Logout Screen Messages +screen.logout.header=Uspje\u0161na odjava +screen.logout.success=Uspje\u0161no ste se odjavili iz Centralnog autentikacijskog servisa +screen.logout.security=Iz sigurnosnih razloga zatvorite web preglednik. +screen.logout.redirect=Servis pomo\u0107u kojeg ste do\u0161li na straice CAS-a prenio je link koji mo\u017eete slijediti pritiskom ovdje. + +screen.service.sso.error.header=Za pristup ovom servisu potrebna je ponovna autentikacija. +screen.service.sso.error.message=Poku\u0161ali ste pristupiti servisu koji zahtijeva autentikaciju, pri \u010demu se niste ponovno autenticirali. Molimo vas poku\u0161ajte se ponovno autenticirati pritiskom ovdje. + +error.invalid.loginticket=Sadr\u017eaj forme ve\u0107 je poslan. Ponovno slanje nije dozvoljeno. +required.username=Korisni\u010dko ime je obavezno polje. +required.password=Zaporka je obavezno polje. +error.authentication.credentials.bad=Korisni\u010dko ime i(li) zaporka nisu ispravni. +error.authentication.credentials.unsupported=CAS ne podr\u017eava ovaj na\u010din autentikacije. + +INVALID_REQUEST_PROXY=Parametri 'pgt' i 'targetService' su obavezni. +INVALID_TICKET_SPEC=Ticket nije pro\u0161ao provjeru ispravnosti. Ova pogre\u0161ka mo\u017ee upu\u0107ivati na poku\u0161aj provjere ispravnosti Proxy Ticketa pomo\u0107u Service Ticket validatora ili na neudovoljavanje zahtjevu uz parametar renew=true. + +INVALID_REQUEST=Parametri 'service' i 'ticket' su obavezni +INVALID_TICKET=Ticket ''{0}'' nije prepoznat. +INVALID_SERVICE=Ticket ''{0}'' ne odgovara ovom servisu. Orginalni servis bio je ''{1}'', a isporu\u010deni servis bio je ''{2}''. + +screen.service.error.header=Aplikacija nije autorizirana za uporabu CAS-a +screen.service.error.message=Aplikacia u koju ste se poku\u0161ali prijaviti nije autorizirana za uporabu CAS-a. diff --git a/cas-server-webapp/src/main/resources/messages_it.properties b/cas-server-webapp/src/main/resources/messages_it.properties new file mode 100644 index 000000000000..bf45e5c02c09 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_it.properties @@ -0,0 +1,103 @@ +#Author: Roberto Cosenza http://robcos.com +#Version: $Revision$ $Date$ +#Since: 3.0.5 + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=Benvenuti al Central Authentication Service (CAS). +screen.welcome.security=Per motivi di sicurezza dovresti effettuare il logout e chiudere tutte le finestre del browser quando hai finito di utilizzare servizi che necessitano autenticazione. +screen.welcome.instructions=Inserisci login e password +screen.welcome.label.netid=Login: +screen.welcome.label.netid.accesskey=L +screen.welcome.label.password=Password: +screen.welcome.label.password.accesskey=P +screen.welcome.label.warn=Avvisami prima di autenticarmi su un altro sito +screen.welcome.label.warn.accesskey=A +screen.welcome.button.login=LOGIN +screen.welcome.button.clear=ANNULLA + +# Blocked Errors Page +screen.blocked.header=Accesso Negato +screen.blocked.message=È stata inserita la password sbagliata troppe volte. L'account è stato bloccato. + +#Confirmation Screen Messages +screen.confirmation.message=Clicca quí per accedere al servizio. + +#Generic Success Screen Messages +screen.success.header=Login eseguito correttamente +screen.success.success=Hai effettuato il login al Central Authentication Service. +screen.success.security=Per motivi di sicurezza dovresti effettuare il logout e chiudere tutte le finestre del browser quando hai finito di utilizzare servizi che necessitano autenticazione. + +#Logout Screen Messages +screen.logout.header=Logout effettuato con successo +screen.logout.success=Hai correttamente effettuato il logout dal Central Authentication Service. +screen.logout.security=Per motivi di sicurezza, si consiglia di chiudere tutte le finestre del browser. +screen.logout.redirect=Puoi rifare il login cliccando quì + +screen.service.sso.error.header=È necessario effettuare nuovamente l'autenticazione per avere l'accesso a questo servizio +screen.service.sso.error.message=Si è tentato di accedere a un servizio che richiede di effettuare nuovamente l'autenticazione. Si prega di autenticarsi nuovamente. + +error.invalid.loginticket=Ricompila il form dall'inizio senza utilizzare il tasto 'indietro' +required.username=Il campo login é obbligatorio +required.password=Il campo password é obbligatorio +error.authentication.credentials.bad=Login o password errate +error.authentication.credentials.unsupported=Le credenziali utilizzate non sono supportate da CAS + +INVALID_REQUEST_PROXY=I parametri 'pgt' e 'targetService' sono entrambi obbligatori +INVALID_TICKET_SPEC=La convalida del Ticket non ha avuto successo. Una possibile causa di errore potrebbe essere il tentativo di convalidare un Proxy Ticket via un Service Ticket validator. +INVALID_REQUEST=I parametri 'service' e 'ticket' sono entrambi obbligatori +INVALID_TICKET=Il ticket ''{0}'' non é stato riconosciuto +INVALID_SERVICE=Il ticket ''{0}'' non corrisponde a nessun servizio disponibile + +#Service Error Messages +screen.service.error.header=Servizio non autorizzato. +screen.service.error.message=Il servizio a cui stai cercando di accedere non é configurato per CAS + +# LPPE Account Error +screen.accounterror.password.message=La data di rinnovo della password non è specificata, è scaduta o non valida. Si prega di contattare l'amministratore di sistema per recuperare le credenziali di accesso. + +# LPPE Account Disabled +screen.accountdisabled.heading=Questo account è disabilitato. +screen.accountdisabled.message=Si prega di contattare l'amministratore di sistema per recuperare le credenziali di accesso. + +# LPPE Password Expired +screen.expiredpass.heading=La vostra password è scaduta. +screen.expiredpass.message=Si prega di cambiare la password. + +# LPPE Password Must be changed +screen.mustchangepass.heading=La password deve essere cambiata. +screen.mustchangepass.message=Si prega di cambiare la password. + +# LPPE Login out of authorized hours +screen.badhours.heading=Non si è autorizzati a effettuare il login a quest'ora. +screen.badhours.message=Si prega di riprovare più tardi. + +# LPPE Login out of authorized workstations +screen.badworkstation.heading=Non si è autorizzati a effettuare il login da questa postazione. +screen.badworkstation.message=Si prega di conttattare l'amministratore di sistema per recuperare le credenziali d'accesso. + +# LPPE Password Warning +screen.warnpass.heading.today=La vostra password scade oggi! +screen.warnpass.heading.tomorrow=La vostra password scade domani! +screen.warnpass.heading.other=La vostra password scade tra {0} giorni. +screen.warnpass.message.line1=Si prega di cambiare la password ora. +screen.warnpass.message.line2=E in corso la redirezione automatica verso la vostra applicazione tra 10 secondi. diff --git a/cas-server-webapp/src/main/resources/messages_ja.properties b/cas-server-webapp/src/main/resources/messages_ja.properties new file mode 100644 index 000000000000..cf7d299140bd --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_ja.properties @@ -0,0 +1,70 @@ +#Author: Shoji Kajita +#Version: $Revision$ $Date$ +#Since: 3.1 + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=\u304a\u3081\u3067\u3068\u3046\u3054\u3056\u3044\u307e\u3059! CAS \u3092\u30aa\u30f3\u30e9\u30a4\u30f3\u306b\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3057\u305f\uff0e\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u8a8d\u8a3c\u30cf\u30f3\u30c9\u30e9\u3067\u306f\uff0c\u30e6\u30fc\u30b6\u540d\u3068\u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u540c\u3058\u3068\u304d\u306b\u8a8d\u8a3c\u3055\u308c\u307e\u3059\uff0e\u305c\u3072\uff0c\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\uff0e +screen.welcome.security=\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u4e0a\u306e\u7406\u7531\u304b\u3089\uff0c\u8a8d\u8a3c\u304c\u5fc5\u8981\u306a\u30b5\u30fc\u30d3\u30b9\u306e\u30a2\u30af\u30bb\u30b9\u7d42\u4e86\u6642\u306b\u306f\uff0c\u30a6\u30a7\u30d6\u30d6\u30e9\u30a6\u30b6\u3092\u30ed\u30b0\u30a2\u30a6\u30c8\u3057\uff0c\u7d42\u4e86\u3057\u3066\u304f\u3060\u3055\u3044\uff0e +screen.welcome.instructions=\u30cd\u30c3\u30c8ID \u304a\u3088\u3073\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044 +screen.welcome.label.netid=\u30cd\u30c3\u30c8ID: +screen.welcome.label.netid.accesskey=n +screen.welcome.label.password=\u30d1\u30b9\u30ef\u30fc\u30c9: +screen.welcome.label.password.accesskey=p +screen.welcome.label.warn=\u4ed6\u306e\u30b5\u30a4\u30c8\u306b\u30ed\u30b0\u30a4\u30f3\u3059\u308b\u524d\u306b\u8b66\u544a\u3092\u51fa\u3059\uff0e +screen.welcome.label.warn.accesskey=w +screen.welcome.button.login=\u30ed\u30b0\u30a4\u30f3 +screen.welcome.button.clear=\u30af\u30ea\u30a2 + +#Confirmation Screen Messages +screen.confirmation.message=\u3053\u3053\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306b\u79fb\u52d5\u3057\u307e\u3059\uff0e + +#Generic Success Screen Messages +screen.success.header=\u30ed\u30b0\u30a4\u30f3\u3057\u307e\u3057\u305f +screen.success.success=Central Authentication Service \u306b\u30ed\u30b0\u30a4\u30f3\u3067\u304d\u307e\u3057\u305f\uff0e +screen.success.security=\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u4e0a\u306e\u7406\u7531\u304b\u3089\uff0c\u8a8d\u8a3c\u304c\u5fc5\u8981\u306a\u30b5\u30fc\u30d3\u30b9\u306e\u30a2\u30af\u30bb\u30b9\u7d42\u4e86\u6642\u306b\u306f\uff0c\u30a6\u30a7\u30d6\u30d6\u30e9\u30a6\u30b6\u3092\u30ed\u30b0\u30a2\u30a6\u30c8\u3057\uff0c\u7d42\u4e86\u3057\u3066\u304f\u3060\u3055\u3044\uff0e + +#Logout Screen Messages +screen.logout.header=\u30ed\u30b0\u30a2\u30a6\u30c8\u3057\u307e\u3057\u305f +screen.logout.success=Central Authentication Service \u3092\u30ed\u30b0\u30a2\u30a6\u30c8\u3067\u304d\u307e\u3057\u305f\uff0e +screen.logout.security=\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u4e0a\u306e\u7406\u7531\u304b\u3089\uff0c\u30a6\u30a7\u30d6\u30d6\u30e9\u30a6\u30b6\u3092\u7d42\u4e86\u3057\u3066\u304f\u3060\u3055\u3044\uff0e +screen.logout.redirect=\u3042\u306a\u305f\u304c\u30a2\u30af\u30bb\u30b9\u3057\u305f\u30b5\u30fc\u30d3\u30b9\u306b\u3088\u308a\u63d0\u4f9b\u3055\u308c\u305f\u30ea\u30f3\u30af\u306b\u30a2\u30af\u30bb\u30b9\u3059\u308b\u3068\u304d\u306f\u3053\u3053\u3092\u30af\u30ea\u30c3\u30af\u3057\u307e\u3059\uff0e + +#Service Error Messages +screen.service.error.header=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306f CAS \u3092\u4f7f\u3046\u6a29\u9650\u304c\u3042\u308a\u307e\u305b\u3093 +screen.service.error.message=\u8a8d\u8a3c\u3057\u3088\u3046\u3068\u3057\u305f\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306f CAS \u3092\u4f7f\u3046\u6a29\u9650\u304c\u3042\u308a\u307e\u305b\u3093\uff0e + +screen.service.sso.error.header=\u3053\u306e\u30b5\u30fc\u30d3\u30b9\u306b\u30a2\u30af\u30bb\u30b9\u3059\u308b\u305f\u3081\u306b\u306f\u518d\u8a8d\u8a3c\u304c\u5fc5\u8981 +screen.service.sso.error.message=\u518d\u8a8d\u8a3c\u3092\u8981\u6c42\u3059\u308b\u30b5\u30fc\u30d3\u30b9\u306b\u30a2\u30af\u30bb\u30b9\u3057\u3088\u3046\u3068\u3057\u307e\u3057\u305f\uff0e\u518d\u8a8d\u8a3c\u3092\u8a66\u307f\u3066\u304f\u3060\u3055\u3044\uff0e + +error.invalid.loginticket=\u3059\u3067\u306b\u9001\u4fe1\u6e08\u307f\u306e\u30d5\u30a9\u30fc\u30e0\u306f\u518d\u9001\u4fe1\u3067\u304d\u307e\u305b\u3093\uff0e +required.username=\u30e6\u30fc\u30b6\u540d\u306f\u5fc5\u9808\u30d5\u30a3\u30fc\u30eb\u30c9\u3067\u3059\uff0e +required.password=\u30d1\u30b9\u30ef\u30fc\u30c9\u306f\u5fc5\u9808\u30d5\u30a3\u30fc\u30eb\u30c9\u3067\u3059\uff0e +error.authentication.credentials.bad=\u3042\u306a\u305f\u304c\u5165\u529b\u3057\u305f\u8a8d\u8a3c\u60c5\u5831\u306f\uff0c\u8a8d\u8a3c\u53ef\u80fd\u306a\u3082\u306e\u3067\u3042\u308b\u3053\u3068\u304c\u78ba\u8a8d\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\uff0e +error.authentication.credentials.unsupported=\u5165\u529b\u3057\u305f\u8a8d\u8a3c\u60c5\u5831\u306f CAS \u3067\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\uff0e + +INVALID_REQUEST_PROXY=\u300cpgt\u300d\u304a\u3088\u3073\u300ctargetService\u300d\u30d1\u30e9\u30e1\u30fc\u30bf\u306e\u4e21\u65b9\u304c\u5fc5\u8981\u3067\u3059 +INVALID_TICKET_SPEC=\u30c1\u30b1\u30c3\u30c8\u306e\u6b63\u5f53\u6027\u57fa\u6e96\u30c1\u30a7\u30c3\u30af\u306b\u5931\u6557\u3057\u307e\u3057\u305f\uff0e\u300c\u30b5\u30fc\u30d3\u30b9\u30c1\u30b1\u30c3\u30c8\u300d\u30d0\u30ea\u30c7\u30fc\u30bf\u306b\u3088\u308b\u300c\u30d7\u30ed\u30af\u30b7\u30c1\u30b1\u30c3\u30c8\u300d\u306e\u6b63\u5f53\u6027\u30c1\u30a7\u30c3\u30af\u3092\u884c\u3063\u305f\u304b\uff0c\u66f4\u65b0\u8981\u6c42\u306e\u898f\u683c\u306b\u3042\u3063\u3066\u3044\u306a\u3044\u30b1\u30fc\u30b9\u304c\u8003\u3048\u3089\u308c\u307e\u3059\uff0e +INVALID_REQUEST=\u300cservice\u300d\u304a\u3088\u3073\u300cticket\u300d\u30d1\u30e9\u30e1\u30fc\u30bf\u306e\u4e21\u65b9\u304c\u5fc5\u8981\u3067\u3059 +INVALID_TICKET=ticket\u300c{0}\u300d\u306f\u8a8d\u8b58\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f +INVALID_SERVICE=ticket\u300c{0}\u300d\u306f\u63d0\u4f9b\u3055\u308c\u3066\u3044\u308b\u30b5\u30fc\u30d3\u30b9\u306b\u4e00\u81f4\u3057\u307e\u305b\u3093 + diff --git a/cas-server-webapp/src/main/resources/messages_mk.properties b/cas-server-webapp/src/main/resources/messages_mk.properties new file mode 100644 index 000000000000..46db8a758a23 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_mk.properties @@ -0,0 +1,68 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# @author \u0412\u0430\u043d\u0433\u0435\u043b \u0410\u0458\u0430\u043d\u043e\u0432\u0441\u043a\u0438 +# \u0418\u043d\u0441\u0442\u0438\u0442\u0443\u0442 \u0437\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0442\u0438\u043a\u0430 +# @since 3.1.1 + +#Welcome Screen Messages +screen.welcome.welcome=\u0414\u043e\u0431\u0440\u043e\u0434\u043e\u0458\u0434\u043e\u0432\u0442\u0435 +screen.welcome.security=\u041f\u043e\u0440\u0430\u0434\u0438 \u0441\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u043d\u0438 \u043f\u0440\u0438\u0447\u0438\u043d\u0438 \u0432\u0435 \u043c\u043e\u043b\u0438\u043c\u0435 \u0434\u0430 \u043d\u0435 \u0437\u0430\u0431\u043e\u0440\u0430\u0432\u0438\u0442\u0435 \u0434\u0430 \u0441\u0435 \u043e\u0434\u0458\u0430\u0432\u0438\u0442\u0435 \u0438 \u0434\u0430 \u0433\u043e \u0437\u0430\u0442\u0432\u043e\u0440\u0438\u0442\u0435 \u0432\u0430\u0448\u0438\u043e\u0442 \u043f\u0440\u0435\u0431\u0430\u0440\u0443\u0432\u0430\u0447 \u043f\u043e \u0437\u0430\u0432\u0440\u0448\u0443\u0432\u0430\u045a\u0435\u0442\u043e \u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u0430 \u0441\u043e \u0430\u043f\u043b\u0438\u043a\u0430\u0446\u0438\u0438\u0442\u0435. +screen.welcome.instructions=\u0412\u043d\u0435\u0441\u0435\u0442\u0435 \u043a\u043e\u0440\u0438\u0441\u043d\u0438\u0447\u043a\u043e \u0438\u043c\u0435 \u0438 \u043b\u043e\u0437\u0438\u043d\u043a\u0430. +screen.welcome.label.netid=\u041a\u043e\u0440\u0438\u0441\u043d\u0438\u0447\u043a\u043e \u0438\u043c\u0435: +screen.welcome.label.netid.accesskey=\u043a +screen.welcome.label.password=\u041b\u043e\u0437\u0438\u043d\u043a\u0430: +screen.welcome.label.password.accesskey=\u043b +screen.welcome.label.warn=\u041f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0434\u0438 \u043c\u0435 \u043f\u0440\u0438 \u043d\u0430\u0458\u0430\u0432\u0443\u0432\u0430\u045a\u0435 \u0432\u043e \u0434\u0440\u0443\u0433\u0438 \u0430\u043f\u043b\u0438\u043a\u0430\u0446\u0438\u0438. +screen.welcome.label.warn.accesskey=\u043f +screen.welcome.button.login=\u041d\u0410\u0408\u0410\u0412\u0410 +screen.welcome.button.clear=\u041f\u041e\u041d\u0418\u0428\u0422\u0418 + +#Confirmation Screen Messages +screen.confirmation.message=\u041f\u0440\u0438\u0442\u0438\u0441\u043d\u0435\u0442\u0435 \u0442\u0443\u043a\u0430 \u0437\u0430 \u0432\u043b\u0435\u0437 \u0432\u043e \u0430\u043f\u043b\u0438\u043a\u0430\u0446\u0438\u0458\u0430\u0442\u0430. + +#Generic Success Screen Messages +screen.success.header=\u0423\u0441\u043f\u0435\u0448\u043d\u0430 \u043d\u0430\u0458\u0430\u0432\u0430 +screen.success.success=\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0435 \u043d\u0430\u0458\u0430\u0432\u0438\u0432\u0442\u0435 \u043d\u0430 \u0426\u0435\u043d\u0442\u0440\u0430\u043b\u043d\u0438\u043e\u0442 \u0410\u0432\u0442\u0435\u043d\u0442\u0438\u043a\u0430\u0446\u0438\u0441\u043a\u0438 \u0421\u0435\u0440\u0432\u0438\u0441. +screen.success.security=\u041f\u043e\u0440\u0430\u0434\u0438 \u0441\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u043d\u0438 \u043f\u0440\u0438\u0447\u0438\u043d\u0438 \u0432\u0435 \u043c\u043e\u043b\u0438\u043c\u0435 \u0434\u0430 \u0441\u0435 \u043e\u0434\u0458\u0430\u0432\u0438\u0442\u0435 \u0438 \u0434\u0430 \u0433\u043e \u0437\u0430\u0442\u0432\u043e\u0440\u0438\u0442\u0435 \u0432\u0430\u0448\u0438\u043e\u0442 browser \u043f\u043e \u0437\u0430\u0432\u0440\u0448\u0443\u0432\u0430\u045a\u0435\u0442\u043e \u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u0430 \u0441\u043e \u0430\u043f\u043b\u0438\u043a\u0430\u0446\u0438\u0438\u0442\u0435. + +#Logout Screen Messages +screen.logout.header=\u0423\u0441\u043f\u0435\u0448\u043d\u0430 \u043e\u0434\u0458\u0430\u0432\u0430 +screen.logout.success=\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0435 \u043e\u0434\u0458\u0430\u0432\u0438\u0432\u0442\u0435 \u043e\u0434 \u0426\u0435\u043d\u0442\u0440\u0430\u043b\u043d\u0438\u043e\u0442 \u0410\u0432\u0442\u0435\u0442\u0438\u043a\u0430\u0446\u0438\u0441\u043a\u0438 \u0421\u0435\u0440\u0432\u0438\u0441. +screen.logout.security=\u041f\u043e\u0440\u0430\u0434\u0438 \u0441\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u043d\u0438 \u043f\u0440\u0438\u0447\u0438\u043d\u0438 \u0432\u0435 \u043c\u043e\u043b\u0438\u043c\u0435 \u0434\u0430 \u0433\u043e \u0437\u0430\u0442\u0432\u043e\u0440\u0438\u0442\u0435 \u0432\u0430\u0448\u0438\u043e\u0442 browser. +screen.logout.redirect=\u0421\u0435\u0440\u0432\u0438\u0441\u043e\u0442 \u043a\u043e\u0458 \u0432\u0435 \u0434\u043e\u043d\u0435\u0441\u0435 \u043d\u0430 \u0426\u0410\u0421 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0438\u0442\u0435, \u043d\u0443\u0434\u0438 \u043b\u0438\u043d\u043a \u043d\u0430 \u043a\u043e\u0458 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u0430 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u0435 \u043d\u0430\u0442\u0430\u043c\u0443 \u0441\u043e \u043f\u0440\u0438\u0442\u0438\u0441\u043a\u0430\u045a\u0435 \u0422\u0423\u041a\u0410. + +screen.service.sso.error.header=\u0417\u0430 \u043f\u0440\u0438\u0441\u0442\u0430\u043f \u043d\u0430 \u043e\u0432\u043e\u0458 \u0441\u0435\u0440\u0432\u0438\u0441 \u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u043d\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0430\u0432\u0442\u0435\u043d\u0442\u0438\u043a\u0430\u0446\u0438\u0458\u0430 +screen.service.sso.error.message=\u0421\u0435 \u043e\u0431\u0438\u0434\u043e\u0432\u0442\u0435 \u0434\u0430 \u043f\u0440\u0438\u0441\u0442\u0430\u043f\u0438\u0442\u0435 \u043d\u0430 \u0441\u0435\u0440\u0432\u0438\u0441 \u043a\u043e\u0458 \u043f\u043e\u0431\u0430\u0440\u0443\u0432\u0430 \u0430\u0432\u0442\u0435\u043d\u0442\u0438\u043a\u0430\u0446\u0438\u0458\u0430, \u043f\u0440\u0438\u0442\u043e\u0430 \u043d\u0435 \u0441\u0442\u0435 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0430\u0432\u0442\u0435\u043d\u0442\u0438\u0446\u0438\u0440\u0430\u043d\u0438. \u0412\u0435 \u043c\u043e\u043b\u0438\u043c\u0435 \u0434\u0430 \u0441\u0435 \u043e\u0431\u0438\u0434\u0435\u0442\u0435 \u0434\u0430 \u0441\u0435 \u0430\u0432\u0442\u0435\u0442\u0438\u043d\u0446\u0438\u0440\u0430\u0442\u0435 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0441\u043e \u043f\u0440\u0438\u0442\u0438\u0441\u043a\u0430\u045a\u0435 \u0422\u0423\u041a\u0410. + +error.invalid.loginticket=\u0421\u043e\u0434\u0440\u0436\u0438\u043d\u0430\u0442\u0430 \u043d\u0430 \u0444\u043e\u0440\u043c\u0443\u043b\u0430\u0440\u043e\u0442 \u0435 \u0432\u0435\u045c\u0435 \u0438\u0441\u043f\u0440\u0430\u0442\u0435\u043d\u0430. \u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0438\u0441\u043f\u0440\u0430\u045c\u0430\u045a\u0435 \u043d\u0435 \u0435 \u0434\u043e\u0437\u0432\u043e\u043b\u0435\u043d\u043e. +required.username=\u041a\u043e\u0440\u0438\u0441\u043d\u0438\u0447\u043a\u043e\u0442\u043e \u0438\u043c\u0435 \u0437\u0430\u0434\u043e\u043b\u0436\u0438\u0442\u0435\u043b\u043d\u043e \u0442\u0440\u0435\u0431\u0430 \u0434\u0430 \u0441\u0435 \u043f\u043e\u043f\u043e\u043b\u043d\u0438. +required.password=\u041b\u043e\u0437\u0438\u043d\u043a\u0430\u0442\u0430 \u0437\u0430\u0434\u043e\u043b\u0436\u0438\u0442\u0435\u043b\u043d\u043e \u0442\u0440\u0435\u0431\u0430 \u0434\u0430 \u0441\u0435 \u043f\u043e\u043f\u043e\u043b\u043d\u0438 +error.authentication.credentials.bad=\u041a\u043e\u0440\u0438\u0441\u043d\u0438\u0447\u043a\u043e\u0442\u043e \u0438\u043c\u0435 \u0438/\u0438\u043b\u0438 \u043b\u043e\u0437\u0438\u043d\u043a\u0430\u0442\u0430 \u043d\u0435 \u0441\u0435 \u0438\u0441\u043f\u0440\u0430\u0432\u043d\u0438. +error.authentication.credentials.unsupported=\u0426\u0410\u0421 \u043d\u0435 \u0433\u043e \u043f\u043e\u0434\u0434\u0440\u0436\u0443\u0432\u0430 \u043e\u0432\u043e\u0458 \u043d\u0430\u0447\u0438\u043d \u043d\u0430 \u0430\u0432\u0442\u0435\u043d\u0442\u0438\u043a\u0430\u0446\u0438\u0458\u0430. + +INVALID_REQUEST_PROXY=\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0442\u0435 'pgt' \u0438 'targetService' \u0441\u0435 \u0437\u0430\u0434\u043e\u043b\u0436\u0438\u0442\u0435\u043b\u043d\u0438. +INVALID_TICKET_SPEC=\u0411\u0438\u043b\u0435\u0442\u043e\u0442 \u043d\u0435 \u0458\u0430 \u043f\u043e\u043c\u0438\u043d\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430\u0442\u0430 \u043d\u0430 \u0438\u0441\u043f\u0440\u0430\u0432\u043d\u043e\u0441\u0442. \u041e\u0432\u0430\u0430 \u0433\u0440\u0435\u0448\u043a\u0430 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0443\u043f\u0430\u0442\u0443\u0432\u0430 \u043d\u0430 \u043e\u0431\u0438\u0434 \u043d\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430 \u0438\u0441\u043f\u0440\u0430\u0432\u043d\u043e\u0441\u0442 \u043d\u0430 \u041f\u043e\u0441\u0440\u0435\u0434\u043d\u0438\u043a \u0411\u0438\u043b\u0435\u0442 \u0441\u043e \u043f\u043e\u043c\u043e\u0448 \u043d\u0430 \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440 \u043d\u0430 \u0421\u0435\u0440\u0432\u0438\u0441\u0435\u043d \u0411\u0438\u043b\u0435\u0442 \u0438\u043b\u0438 \u043d\u0430 \u043d\u0435\u0437\u0430\u0434\u043e\u0432\u043e\u043b\u0443\u0432\u0430\u045a\u0435 \u043d\u0430 \u0431\u0430\u0440\u0430\u045a\u0430\u0442\u0430 \u0441\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0430\u0440\u043e\u0442 renew=true. + +INVALID_REQUEST=\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0442\u0435 'service' \u0438 'ticket' \u0441\u0435 \u0437\u0430\u0434\u043e\u043b\u0436\u0438\u0442\u0435\u043b\u043d\u0438. +INVALID_TICKET=\u0411\u0438\u043b\u0435\u0442\u043e\u0442 ''{0}'' \u043d\u0435 \u0435 \u043f\u0440\u0435\u043f\u043e\u0437\u043d\u0430\u0442. +INVALID_SERVICE=\u0411\u0438\u043b\u0435\u0442\u043e\u0442 ''{0}'' \u043d\u0435 \u043e\u0434\u0433\u043e\u0432\u0430\u0440\u0430 \u043d\u0430 \u043e\u0432\u043e\u0458 \u0441\u0435\u0440\u0432\u0438\u0441. \u041e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u043d\u0438\u043e\u0442 \u0441\u0435\u0440\u0432\u0438\u0441 \u0431\u0435\u0448\u0435 ''{1}'', \u0430 \u0438\u0441\u043f\u043e\u0440\u0430\u0447\u0430\u043d\u0438\u043e\u0442 \u0441\u0435\u0440\u0432\u0438\u0441 \u0435 ''{2}''. + +screen.service.error.header=\u0410\u043f\u043b\u0438\u043a\u0430\u0446\u0438\u0458\u0430\u0442\u0430 \u043d\u0435 \u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0438\u0440\u0430\u043d\u0430 \u0437\u0430 \u043a\u043e\u0440\u0438\u0441\u0442\u0435\u045a\u0435 \u043d\u0430 \u0426\u0410\u0421 +screen.service.error.message=\u0410\u043f\u043b\u0438\u043a\u0430\u0446\u0438\u0458\u0430\u0442\u0430 \u0432\u043e \u043a\u043e\u0458\u0430 \u0441\u0435 \u043e\u0431\u0438\u0434\u0443\u0432\u0430\u0442\u0435 \u0434\u0430 \u0441\u0435 \u043d\u0430\u0458\u0430\u0432\u0438\u0442\u0435 \u043d\u0435 \u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0438\u0440\u0430\u043d\u0430 \u0437\u0430 \u043a\u043e\u0440\u0438\u0441\u0442\u0435\u045a\u0435 \u043d\u0430 \u0426\u0410\u0421. diff --git a/cas-server-webapp/src/main/resources/messages_nl.properties b/cas-server-webapp/src/main/resources/messages_nl.properties new file mode 100644 index 000000000000..a9b42a07e716 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_nl.properties @@ -0,0 +1,65 @@ +#Author: Jan "Velpi" Van der Velpen +#Version $Revision$ $Date$ +#Since 3.0.3 + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=Proficiat met de succesvolle installatie van CAS! Met de standaard "authentication handler" kan je ingeloggen als de gebruikersnaam gelijk is aan het wachtwoord. Je kan het nu proberen. +screen.welcome.security=Voor de veiligheid moet je uitloggen en je browser sluiten wanneer je geen toegang meer nodig hebt tot afgeschermde applicaties! +screen.welcome.instructions=Om verder te gaan dien je jezelf te authenticeren. +screen.welcome.label.netid.accesskey=g +screen.welcome.label.netid=Gebruikersnaam: +screen.welcome.label.password=Wachtwoord: +screen.welcome.label.password.accesskey=w +screen.welcome.label.warn=Vraag toestemming vooraleer me ingelogd door te sturen naar andere sites. +screen.welcome.label.warn.accesskey=v +screen.welcome.button.login=LOGIN +screen.welcome.button.clear=CLEAR + +#Confirmation Screen Messages +screen.confirmation.message=Doorgaan naar de applicatie. + +#Generic Success Screen Messages +screen.success.header=Succesvol ingelogd. +screen.success.success=Je bent ingelogd bij de Central Authentication Service. +screen.success.security=Voor de veiligheid moet je uitloggen en je browser sluiten wanneer je geen toegang meer nodig hebt tot afgeschermde applicaties! + +#Logout Screen Messages +screen.logout.header=Succesvol uitgelogd. +screen.logout.success=Je bent nu uitgelogd bij de Central Authentication Service. +screen.logout.security=Voor de veiligheid dien je je browser nu af te sluiten. +screen.logout.redirect=De applicatie waar je vandaan komt heeft deze link opgegeven die je kan volgen door hier te klikken. + +error.invalid.loginticket=Je mag geen formulier verzenden dat je al eens hebt verzonden. +required.username=Gelieve een gebruikersnaam in te vullen. +required.password=Gelieve een wachtwoord in te vullen. +error.authentication.credentials.bad=De combinatie van gebruikersnaam en wachtwoord was niet juist. +error.authentication.credentials.unsupported=De verstuurde identificatiegegevens worden niet ondersteund door CAS. + +INVALID_REQUEST_PROXY='pgt' en 'targetService' zijn verplichte parameters. +INVALID_TICKET_SPEC=Het ticket kwam niet overeen met de specificatie voor validatie. Misschien probeer je een Proxy Ticket te valideren op de Service Ticket validator, of komt "renew true" niet overeen. +INVALID_REQUEST='service' en 'ticket' zijn verplichte parameters. +INVALID_TICKET=ticket ''{0}'' is niet gekend. +INVALID_SERVICE=ticket ''{0}'' komt niet overeen met de opgegeven service. + +screen.service.error.header=Geen toegang. +screen.service.error.message=De applicatie waarvoor je toegang vroeg heeft geen toestemming om deze CAS te gebruiken. diff --git a/cas-server-webapp/src/main/resources/messages_pl.properties b/cas-server-webapp/src/main/resources/messages_pl.properties new file mode 100644 index 000000000000..61ca1e28e8ac --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_pl.properties @@ -0,0 +1,142 @@ +# @author Maja Gorecka-Wolniewicz +# @since 3.1.1 + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=Gratulujemy, us\u0142uga CAS jest gotowa do dzia\u0142ania! Domy\u015blny tryb uwierzytelniania akceptuje dane, \ +w kt\u00f3rych has\u0142o jest takie samo jak nazwa u\u017cytkownika - spr\u00f3buj! +screen.welcome.security=Dla zachowania bezpiecze\u0144stwa, gdy zako\u0144czysz korzystanie z us\u0142ug wymagaj\u0105cych uwierzytelnienia, \ +wyloguj si\u0119 i zamknij przegl\u0105dark\u0119! +screen.welcome.instructions=Wprowad\u017a sw\u00f3j identyfikator sieciowy i has\u0142o +screen.welcome.label.netid=Identyfikator: +screen.welcome.label.netid.accesskey=i +screen.welcome.label.password=Has\u0142o: +screen.welcome.label.password.accesskey=h +screen.welcome.label.publicstation=Pracuj\u0119 na publicznej stacji roboczej. +screen.welcome.label.warn=Ostrzegaj mnie przed zalogowaniem na innych serwerach. +screen.welcome.label.warn.accesskey=o +screen.welcome.button.login=ZALOGUJ +screen.welcome.button.clear=WYCZY\u015a\u0106 + +screen.cookies.disabled.title=Przegl\u0105darka ma wy\u0142\u0105czone ciasteczka +screen.cookies.disabled.message=Twoja przegl\u0105darka ma wy\u0142\u0105czone pliki cookie. Pojedyncze logowanie NIE B\u0118DZIE DZIA\u0141A\u0141O. + +screen.aup.button.accept=ZAAKCEPTUJ +screen.aup.button.cancel=ANULUJ + +screen.nonsecure.title=Po\u0142\u0105czenie niezabezpieczone +screen.nonsecure.message=\u0141\u0105czysz si\u0119 do CAS niezabezpieczonym po\u0142\u0105czeniem. Pojedyncze logowanie NIE B\u0118DZIE DIZA\u0141A\u0141O. \ +Aby pojedyncze logowanie funkcjonowa\u0142o, MUSISZ uwierzytelni\u0107 si\u0119 po HTTPS. + +logo.title=Id\u017a na stron\u0119 domow\u0105 Jasig +copyright=Copyright © 2005–2012 Jasig, Inc. Wszelkie prawa zastrze\u017cone. +screen.capslock.on = CAPSLOCK jest w\u0142\u0105czony! + +# Remember-Me Authentication +screen.rememberme.checkbox.title=Zapami\u0119taj mnie + +# Blocked Errors Page +screen.blocked.header=Dost\u0119p zabroniony +screen.blocked.message=Wprowadzono nieprawid\u0142owe has\u0142o zbyt wiele razy. Dost\u0119p zosta\u0142 ograniczony. + +#Confirmation Screen Messages +screen.confirmation.message=Naci\u015bnij tutaj, by przej\u015bc do aplikacji. + +#Generic Success Screen Messages +screen.success.header=Udane logowanie +screen.success.success=Zalogowa\u0142e\u015b si\u0119 w CAS - Centralnej Us\u0142udze Uwierzytelniania. +screen.success.security=Dla zachowania bezpiecze\u0144stwa, gdy zako\u0144czysz korzystanie z us\u0142ug wymagaj\u0105cych uwierzytelnienia, \ +wyloguj si\u0119 i zamknij przegl\u0105dark\u0119! + +#Logout Screen Messages +screen.logout.header=Udane wylogowanie +screen.logout.success=Wylogowa\u0142e\u015b si\u0119 z CAS - Centralnej Us\u0142ugi Uwierzytelniania. +screen.logout.security=Dla zachowania bezpiecze\u0144stwa zamknij przegl\u0105dark\u0119. +screen.logout.redirect=Us\u0142uga przekaza\u0142a adres, do kt\u00f3rego przejdziesz naciskaj\u0105c tutaj. + +screen.service.sso.error.header=W celu dost\u0119pu do us\u0142ugi wymagane jest ponowne uwierzytelnienie +screen.service.sso.error.message=Pr\u00f3ba dost\u0119pu do us\u0142ugi, kt\u00f3ra wymaga ponownego uwierzytelnienia. Pon\u00f3w uwierzytelnienie. +screen.service.required.message=Pr\u00f3bujesz uwierzytelni\u0107 si\u0119 nie podaj\u0105c aplikacji docelowej. Sprawd\u017a prosz\u0119 zapytanie i spr\u00f3buj ponownie. + +error.invalid.loginticket=Nie mo\u017cesz ponownie wys\u0142a\u0107 formularza wcze\u015bniej wys\u0142anego. +username.required=Nazwa u\u017cytkownika jest polem wymaganym. +password.required=Has\u0142o jest polem wymaganym. + +# Authentication failure messages +authenticationFailure.AccountDisabledException=Konto zosta\u0142o wy\u0142\u0105czone. +authenticationFailure.AccountLockedException=Konto zosta\u0142o zablokowane. +authenticationFailure.CredentialExpiredException=Termin wa\u017cno\u015bci has\u0142a up\u0142yn\u0105\u0142. +authenticationFailure.InvalidLoginLocationException=Nie mo\u017cesz zalogowa\u0107 si\u0119 z tego komputera. +authenticationFailure.InvalidLoginTimeException=Nie mo\u017cesz zalogowa\u0107 si\u0119 w tym czasie. +authenticationFailure.AccountNotFoundException=Dostarczone dane uwierzytelniania nie mog\u0105 zosta\u0107 uznane za poprawne. +authenticationFailure.FailedLoginException=Dostarczone dane uwierzytelniania nie mog\u0105 zosta\u0107 uznane za poprawne. +authenticationFailure.UNKNOWN=Dostarczone dane uwierzytelniania nie nie s\u0105 akceptowane przez us\u0142ug\u0119 CAS. + +INVALID_REQUEST_PROXY=parametry 'pgt' i 'targetService' s\u0105 wymagane +INVALID_TICKET_SPEC=Bilet niezgodny ze specyfikacj\u0105. Mo\u017cliwe przyczyny b\u0142\u0119du to pr\u00f3ba sprawdzenia biletu proxy \ +za pomoc\u0105 walidatora biletu us\u0142ugi, lub brak zgodno\u015bci ze zleceniem odnowienia uwierzytelnienia. +INVALID_REQUEST=parametry 'service' i 'ticket' s\u0105 wymagane +INVALID_TICKET=nieznana posta\u0107 biletu ''{0}'' +INVALID_SERVICE=Bilet ''{0}'' nie nale\u017cy do tej us\u0142ugi. Oryginalna us\u0142uga to ''{1}'', aktualna us\u0142uga to ''{2}''. +INVALID_PROXY_CALLBACK=Podany URL po\u015brednika ''{0}'' nie zosta\u0142 uwierzytelniony. +UNAUTHORIZED_SERVICE_PROXY=Podana us\u0142uga ''{0}'' nie ma uprawnie\u0144 do korzystania z CAS. + +screen.service.error.header=Brak uprawnie\u0144 do korzystania z CAS +service.not.authorized.missing.attr=Nie mo\u017cesz korzysta\u0107 z aplikacji gdy\u017c Twoje konto nie ma uprawnie\u0144 do wymaganych przez CAS \ +do uwierzytelnienia w tej aplikacji. Prosz\u0119 skontaktowa\u0107 si\u0119 z pomoc\u0105 techniczn\u0105 w celu uzyskania dost\u0119pu. +screen.service.error.message=Aplikacja, w kt\u00f3rej chcia\u0142e\u015b zosta\u0107 uwierzytelniony nie ma uprawnie\u0144 do korzystania z CAS. +screen.service.empty.error.message=Rejestr us\u0142ug CAS jest pusty i nie zawiera \u017cadnej definicji. \ +Aplikacja, kt\u00f3re chcia\u0142eyby zosta\u0107 uwierzytelnione w CAS musz\u0105 by\u0107 zarejestrowane w rejestrze us\u0142ug. + +# Password policy +password.expiration.warning=Termin wa\u017cno\u015bci has\u0142a up\u0142ywa za {0} dni. Prosz\u0119 zmieni\u0107 has\u0142o. +password.expiration.loginsRemaining=Pozosta\u0142o {0} autoryzacji zanim b\u0119dziesz MUSIA\u0141/A zmieni\u0107 has\u0142o +screen.accountdisabled.heading=Konto zosta\u0142o wy\u0142\u0105czone. +screen.accountdisabled.message=Prosz\u0119 skontaktowa\u0107 si\u0119 z pomoc\u0105 techniczn\u0105 w celu uzyskania dost\u0119pu. +screen.accountlocked.heading=Konto zosta\u0142o zablokowane. +screen.accountlocked.message=Prosz\u0119 skontaktowa\u0107 si\u0119 z pomoc\u0105 techniczn\u0105 w celu uzyskania dost\u0119pu. + +# LPPE Password Expired +screen.expiredpass.heading=Termin wa\u017cno\u015bci has\u0142a up\u0142yn\u0105\u0142. +screen.expiredpass.message=Prosz\u0119 zmieni\u0107 has\u0142o. + +# LPPE Password Must be changed +screen.mustchangepass.heading=Musisz zmieni\u0107 has\u0142o. +screen.mustchangepass.message=Prosz\u0119 zmieni\u0107 has\u0142o. + +# LPPE Login out of authorized hours +screen.badhours.heading=Nie mo\u017cesz zalogowa\u0107 si\u0119 w tym czasie. +screen.badhours.message=Prosz\u0119 spr\u00f3bowa\u0107 p\u00f3\u017aniej. + +# LPPE Login out of authorized workstations +screen.badworkstation.heading=Nie mo\u017cesz zalogowa\u0107 si\u0119 z tego komputera. +screen.badworkstation.message=Prosz\u0119 skontaktowa\u0107 si\u0119 z pomoc\u0105 techniczn\u0105 w celu uzyskania dost\u0119pu. + +# OAuth +screen.oauth.confirm.header=Autoryzacja +screen.oauth.confirm.message=Czy chcesz udost\u0119pni\u0107 ca\u0142y sw\u00f3j profil "{0}"? +screen.oauth.confirm.allow=Udost\u0119pnij + +# Unavailable +screen.unavailable.heading=CAS jest niedost\u0119pny +screen.unavailable.message=Wyst\u0105pi\u0142 b\u0142\u0105d podczas obs\u0142ugi zlecenia. \ +Prosz\u0119 spr\u00f3bowa\u0107 jeszcze raz lub skontaktowa\u0107 si\u0119 z pomoc\u0105 techniczn\u0105. diff --git a/cas-server-webapp/src/main/resources/messages_pt_BR.properties b/cas-server-webapp/src/main/resources/messages_pt_BR.properties new file mode 100644 index 000000000000..e80b4cd72c82 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_pt_BR.properties @@ -0,0 +1,64 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=Parab\u00e9ns por colocar o CAS no ar! O autenticador padr\u00e3o usa Nome de Usu\u00e1rio igual a Senha: v\u00e1 em frente e tente! +screen.welcome.security=Por raz\u00f5es de seguran\u00e7a, por favor deslogue e feche o seu navegador quando terminar de acessar os servi\u00e7os que precisam de autentica\u00e7\u00e3o! +screen.welcome.instructions=Entre com seu usu\u00e1rio e Senha +screen.welcome.label.netid=Usu\u00e1rio: +screen.welcome.label.netid.accesskey=u +screen.welcome.label.password=Senha: +screen.welcome.label.password.accesskey=s +screen.welcome.label.warn=Avisar anter de logar em outros sites. +screen.welcome.label.warn.accesskey=a +screen.welcome.button.login=ENTRAR +screen.welcome.button.clear=LIMPAR + +#Confirmation Screen Messages +screen.confirmation.message=Clique aqui para ir para a aplica\u00e7\u00e3o. + +#Generic Success Screen Messages +screen.success.header=Sucesso ao se logar +screen.success.success=Voc\u00ea se logou com sucesso no Servi\u00e7o de Autentica\u00e7\u00e3o Central. +screen.success.security=Por raz\u00f5es de seguran\u00e7a, por favor efetue um Logout e feche seu navegador quando voc\u00ea terminar de acessar os servi\u00e7os que precisam de autentica\u00e7\u00e3o! + +#Logout Screen Messages +screen.logout.header=Sucesso ao se deslogar +screen.logout.success=Voc\u00ea se deslogou com sucesso no Servi\u00e7o de Autentica\u00e7\u00e3o Central. +screen.logout.security=Por raz\u00f5es de seguran\u00e7a, feche o seu navegador. +screen.logout.redirect=O servi\u00e7o de onde voc\u00ea veio fornecer um link que voc\u00ea pode seguir clicando aqui. + +screen.service.sso.error.header=Re-Autenti\u00e7\u00e3o Obrigat\u00f3ria para Acessar esse Servi\u00e7o +screen.service.sso.error.message=Voc\u00ea tentou acessar um servi\u00e7o que necessita de autentica\u00e7\u00e3o sem re-autentica\u00e7\u00e3o. Por favor, tente autenticar novamente. + +error.invalid.loginticket=Voc\u00ea n\u00e3o pode tentar re-enviar um formul\u00e1rio que j\u00e1 vou enviado anteriormente. +required.username=Usu\u00e1rio \u00e9 um campo obrigat\u00f3rio. +required.password=Senha \u00e9 um campo obrigat\u00f3rio. +error.authentication.credentials.bad=Usu\u00e1rio ou senha inv\u00e1lidos. +error.authentication.credentials.unsupported=As credenciais fornecidas n\u00e3o n\u00e3o suportadas pelo CAS. + +INVALID_REQUEST_PROXY='pgt' e 'targetService' s\u00e3o par\u00e2metros obrigat\u00f3rios +INVALID_TICKET_SPEC=O Ticket falhou a valida\u00e7\u00e3o da especifica\u00e7\u00e3o. Possiveis erros incluem tentativa de validar um Proxy Ticket por meio de um validador Service Ticket, ou n\u00e3o estar de acordo com o pedido de renova\u00e7\u00e3o. +INVALID_REQUEST='service' e 'ticket' s\u00e3o par\u00e2metros obrigat\u00f3rios +INVALID_TICKET=ticket ''{0}'' n\u00e3o reconhecido +INVALID_SERVICE=ticket ''{0}'' n\u00e3o casa com o servi\u00e7o fornecido. O servi\u00e7o original era ''{1}'' e o servi\u00e7o fornecido era ''{2}''. + +screen.service.error.header=Aplica\u00e7\u00e3o n\u00e3o Autorizada a usar o CAS +screen.service.error.message=A aplica\u00e7\u00e3o que voc\u00ea tentou autenticar n\u00e3o \u00e9 autorizada a usar o CAS. diff --git a/cas-server-webapp/src/main/resources/messages_pt_PT.properties b/cas-server-webapp/src/main/resources/messages_pt_PT.properties new file mode 100644 index 000000000000..046364b76cd7 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_pt_PT.properties @@ -0,0 +1,69 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=Parab\u00e9ns! O CAS est\u00e1 agora online! O autenticador padr\u00e3o usa o nome de utilizador igual \u221a\u2020 palavra-passe: v\u00e1 em frente e experimente! +screen.welcome.security=Por quest\u00f5es de seguran\u00e7a, por favor feche o seu browser quando terminar de aceder aos servi\u00e7os que necessitam de autentica\u00e7\u00e3o! +screen.welcome.instructions=Insira o seu utilizador e respectiva palavra-passe +screen.welcome.label.netid=Utilizador: +screen.welcome.label.netid.accesskey=u +screen.welcome.label.password=Palavra-passe: +screen.welcome.label.password.accesskey=p +screen.welcome.label.warn=Avise-me antes de entrar noutros sites. +screen.welcome.label.warn.accesskey=A +screen.welcome.button.login=ENTRAR +screen.welcome.button.clear=LIMPAR + +# Blocked Errors Page +screen.blocked.header=Accesso Bloqueado +screen.blocked.message=Inseriu a palavra-chave incorrectamente demasiadas vezes. A sua conta foi bloqueada. + +#Confirmation Screen Messages +screen.confirmation.message=Clique aqui para ir para a aplica\u00e7\u00e3o. + +#Generic Success Screen Messages +screen.success.header=Sess\u00e3o iniciada com sucesso. +screen.success.success=A sua sess\u00e3o no Servi\u00e7o de Autentica\u00e7\u00e3o Central foi iniciada com sucesso. +screen.success.security=Por raz\u00f5es de seguran\u00e7a, por favor fa\u00e7a Logout e feche o seu browser quando terminar de aceder aos servi\u00e7os que necessitam de autentica\u00e7\u00e3o! + + +#Logout Screen Messages +screen.logout.header=Sess\u00e3o terminada com sucesso. +screen.logout.success=A sua sess\u00e3o no Servi\u00e7o de Autentica\u00e7\u00e3o Central foi terminada com sucesso. +screen.logout.security=Por raz\u00f5es de seguran\u00e7a, por favor feche o seu browser. +screen.logout.redirect=O servi\u00e7o de origem providenciou um link que pode ser seguido ao clicar aqui. + +screen.service.sso.error.header=\u221a\u00e2 necess\u221a\u00b0ria reautentica\u221a\u00df\u221a\u00a3o para aceder a este servi\u221a\u00dfo +screen.service.sso.error.message=Voc\u221a\u2122 tentou o acesso a um servi\u221a\u00dfo que requer reautentica\u221a\u00df\u221a\u00a3o sem a efectuar. Por favor tente autenticar-se novamente. + +error.invalid.loginticket=N\u221a\u00a3o pode tentar reenviar um formul\u221a\u00b0rio que foi enviado anteriormente. +required.username=Utilizador \u221a\u00a9 um campo obrigat\u221a\u2265rio. +required.password=Palavra-passe \u221a\u00a9 um campo obrigat\u221a\u2265rio. +error.authentication.credentials.bad=Utilizador ou palavra-passe inv\u221a\u00b0lidos. +error.authentication.credentials.unsupported=As credenciais fornecidas n\u221a\u00a3o s\u221a\u00a3o suportadas pelo Servi\u221a\u00dfo de Autentica\u221a\u00df\u221a\u00a3o Central. + +INVALID_REQUEST_PROXY=Os par\u221a\u00a2metros 'pgt' e 'targetService' s\u221a\u00a3o obrigat\u221a\u2265rios +INVALID_TICKET_SPEC=O Ticket falhou a valida\u221a\u00df\u221a\u00a3o de especifica\u221a\u00df\u221a\u00a3o. Poder\u221a\u00a3o ser causas a tentativa de validar um Proxy Ticket atr\u221a\u00b0v\u221a\u00a9s de um validador Service Ticket ou n\u221a\u00a3o estar de acordo com o pedido de renova\u221a\u00df\u221a\u00a3o. +INVALID_REQUEST=Os par\u221a\u00a2metros 'service' e 'ticket' s\u221a\u00a3o obrigat\u221a\u2265rios +INVALID_TICKET=ticket ''{0}'' n\u221a\u00a3o reconhecido +INVALID_SERVICE=ticket ''{0}'' n\u221a\u00a3o coincide com o servi\u221a\u00dfo fornecido. O servi\u221a\u00dfo original foi ''{1}'' e o servi\u221a\u00dfo fornecido foi ''{2}''. + +screen.service.error.header=Aplica\u221a\u00df\u221a\u00a3o n\u221a\u00a3o autorizada a usar o Servi\u221a\u00dfo de Autentica\u221a\u00df\u221a\u00a3o Central +screen.service.error.message=A aplica\u221a\u00df\u221a\u00a3o onde se tentou autenticar n\u221a\u00a3o est\u221a\u00b0 autorizada a usar o Servi\u221a\u00dfo de Autentica\u221a\u00df\u221a\u00a3o Central diff --git a/cas-server-webapp/src/main/resources/messages_ru.properties b/cas-server-webapp/src/main/resources/messages_ru.properties new file mode 100644 index 000000000000..f7a92a2f5f8e --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_ru.properties @@ -0,0 +1,107 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=\u041F\u043E\u0437\u0434\u0440\u0430\u0432\u043B\u044F\u0435\u043C \u0441 \u0443\u0441\u043F\u0435\u0448\u043D\u044B\u043C \u0437\u0430\u043F\u0443\u0441\u043A\u043E\u043C \u0441\u0438\u0441\u0442\u0435\u043C\u044B CAS! "Authentication handler", \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0439 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E, \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u043F\u043E\u0434\u043B\u0438\u043D\u043D\u043E\u0441\u0442\u0438 \u0432 \u0442\u043E\u043C \u0441\u043B\u0443\u0447\u0430\u0435 \u0435\u0441\u043B\u0438 \u0438\u043C\u044F \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F \u0438 \u043F\u0430\u0440\u043E\u043B\u044C \u0441\u043E\u0432\u043F\u0430\u0434\u0430\u044E\u0442: \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u0441\u0438\u0441\u0442\u0435\u043C\u0443 CAS \u0432 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0438. +screen.welcome.security=\u0412 \u0446\u0435\u043B\u044F\u0445 \u043D\u0430\u0434\u0435\u0436\u043D\u043E\u0433\u043E \u0443\u0440\u043E\u0432\u043D\u044F \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\u0441\u0442\u0438, \u043F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430, \u0432\u044B\u0439\u0434\u0438\u0442\u0435 \u0438\u0437 \u0441\u0438\u0441\u0442\u0435\u043C\u044B, \u0430 \u0442\u0430\u043A\u0436\u0435 \u0437\u0430\u043A\u0440\u043E\u0439\u0442\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440, \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u0432 \u0434\u043E\u0441\u0442\u0443\u043F \u043A \u0441\u0435\u0440\u0432\u0438\u0441\u0443 \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u043D\u0443\u0436\u0434\u0430\u0435\u0442\u0441\u044F \u0432 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0438 \u043F\u043E\u0434\u043B\u0438\u043D\u043D\u043E\u0441\u0442\u0438! +screen.welcome.instructions=\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043B\u043E\u0433\u0438\u043D \u0438 \u043F\u0430\u0440\u043E\u043B\u044C +screen.welcome.label.netid=\u041B\u043E\u0433\u0438\u043D: +screen.welcome.label.netid.accesskey=\u043B +screen.welcome.label.password=\u041F\u0430\u0440\u043E\u043B\u044C: +screen.welcome.label.password.accesskey=\u043F +screen.welcome.label.warn=\u041F\u0440\u0435\u0434\u0443\u043F\u0440\u0435\u0434\u0438\u0442\u044C \u043F\u0435\u0440\u0435\u0434 \u0432\u0445\u043E\u0434\u043E\u043C \u043D\u0430 \u0434\u0440\u0443\u0433\u0438\u0435 \u0441\u0430\u0439\u0442\u044B. +screen.welcome.label.warn.accesskey=\u0440 +screen.welcome.button.login=\u0412\u041E\u0419\u0422\u0418 +screen.welcome.button.clear=\u041E\u0427\u0418\u0421\u0422\u0418\u0422\u042C + +logo.title=\u043F\u0435\u0440\u0435\u0439\u0442\u0438 \u043D\u0430 \u0434\u043E\u043C\u0430\u0448\u043D\u044E\u044E \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443 Apereo +copyright=Copyright © 2005–2015 Apereo, Inc. \u0412\u0441\u0435 \u043F\u0440\u0430\u0432\u0430 \u0437\u0430\u0449\u0438\u0449\u0435\u043D\u044B. + +# Blocked Errors Page +screen.blocked.header=\u0421\u0435\u0440\u0432\u0438\u0441 \u043D\u0435 \u0438\u043C\u0435\u0435\u0442 \u043F\u0440\u0430\u0432\u0430 \u0434\u043E\u0441\u0442\u0443\u043F\u0430. +screen.blocked.message=\u0412\u044B \u0432\u0432\u0435\u043B\u0438 \u043D\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u044B\u0435 \u043B\u043E\u0433\u0438\u043D \u0438\u043B\u0438 \u043F\u0430\u0440\u043E\u043B\u044C \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043C\u043D\u043E\u0433\u043E \u0440\u0430\u0437. \u0414\u043E\u0441\u0442\u0443\u043F \u043A \u0441\u0435\u0440\u0432\u0438\u0441\u0443 \u043F\u0435\u0440\u0435\u043A\u0440\u044B\u0442. + +#Confirmation Screen Messages +screen.confirmation.message=\u041D\u0430\u0436\u043C\u0438\u0442\u0435 \u0441\u044E\u0434\u0430 \u0434\u043B\u044F \u043F\u0435\u0440\u0435\u0445\u043E\u0434\u0430 \u0432 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435. + +#Generic Success Screen Messages +screen.success.header=\u0412\u0445\u043E\u0434 \u0432 \u0441\u0438\u0441\u0442\u0435\u043C\u0443 \u0443\u0441\u043F\u0435\u0448\u0435\u043D. +screen.success.success=\u0412\u044B \u0443\u0441\u043F\u0435\u0448\u043D\u043E \u0432\u043E\u0448\u043B\u0438 \u0432 \u0441\u0438\u0441\u0442\u0435\u043C\u0443 Central Authentication Service. +screen.success.security=\u0412 \u0446\u0435\u043B\u044F\u0445 \u043D\u0430\u0434\u0435\u0436\u043D\u043E\u0433\u043E \u0443\u0440\u043E\u0432\u043D\u044F \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\u0441\u0442\u0438, \u043F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430, \u0432\u044B\u0439\u0434\u0438\u0442\u0435 \u0438\u0437 \u0441\u0438\u0441\u0442\u0435\u043C\u044B, \u0430 \u0442\u0430\u043A\u0436\u0435 \u0437\u0430\u043A\u0440\u043E\u0439\u0442\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440, \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u0432 \u0434\u043E\u0441\u0442\u0443\u043F \u043A \u0441\u0435\u0440\u0432\u0438\u0441\u0443 \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u043D\u0443\u0436\u0434\u0430\u0435\u0442\u0441\u044F \u0432 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0438 \u043F\u043E\u0434\u043B\u0438\u043D\u043D\u043E\u0441\u0442\u0438! + +#Logout Screen Messages +screen.logout.header=\u0412\u044B\u0445\u043E\u0434 \u0438\u0437 \u0441\u0438\u0441\u0442\u0435\u043C\u044B \u0443\u0441\u043F\u0435\u0448\u0435\u043D. +screen.logout.success=\u0412\u044B \u0443\u0441\u043F\u0435\u0448\u043D\u043E \u0432\u044B\u0448\u043B\u0438 \u0438\u0437 \u0441\u0438\u0441\u0442\u0435\u043C\u044B Central Authentication Service. +screen.logout.security=\u0412 \u0446\u0435\u043B\u044F\u0445 \u043D\u0430\u0434\u0435\u0436\u043D\u043E\u0433\u043E \u0443\u0440\u043E\u0432\u043D\u044F \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\u0441\u0442\u0438, \u0437\u0430\u043A\u0440\u043E\u0439\u0442\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440. +screen.logout.redirect=\u0421\u0435\u0440\u0432\u0438\u0441, \u043A \u043A\u043E\u0442\u043E\u0440\u043E\u043C\u0443 \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C \u0434\u043E\u0441\u0442\u0443\u043F, \u043F\u0440\u0435\u0434\u043E\u0441\u0442\u0430\u0432\u0438\u043B \u0441\u0441\u044B\u043B\u043A\u0443 \u0434\u043B\u044F \u0432\u0445\u043E\u0434\u0430. + +screen.service.sso.error.header=\u041F\u0435\u0440\u0435\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u043F\u043E\u0434\u043B\u0438\u043D\u043D\u043E\u0441\u0442\u0438 \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u0434\u043B\u044F \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A \u0441\u0435\u0440\u0432\u0438\u0441\u0443. +screen.service.sso.error.message=\u0412\u044B \u043F\u043E\u043F\u044B\u0442\u0430\u043B\u0438\u0441\u044C \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u0434\u043E\u0441\u0442\u0443\u043F \u043A \u0441\u0435\u0440\u0432\u0438\u0441\u0443 \u043A\u043E\u0442\u043E\u0440\u043E\u043C\u0443 \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u043F\u0435\u0440\u0435\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u043F\u043E\u0434\u043B\u0438\u043D\u043D\u043E\u0441\u0442\u0438. \u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430 \u0441\u0434\u0435\u043B\u0430\u0439\u0442\u0435 \u044D\u0442\u043E \u0441\u043D\u043E\u0432\u0430. + +error.invalid.loginticket=\u0412\u0435\u0431-\u0444\u043E\u0440\u043C\u0430 \u0443\u0436\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u043B\u0430 \u0434\u0430\u043D\u043D\u044B\u0435 \u043D\u0430 \u0441\u0435\u0440\u0432\u0435\u0440. \u041F\u043E\u0432\u0442\u043E\u0440\u043D\u0430\u044F \u043F\u0435\u0440\u0435\u043E\u0442\u043F\u0440\u0430\u0432\u043A\u0430 \u0434\u0430\u043D\u043D\u044B\u0445 \u043D\u0435\u0432\u043E\u0437\u043C\u043E\u0436\u043D\u0430. +required.username=\u0418\u043C\u044F \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F - \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435 \u043F\u043E\u043B\u0435 \u0432\u0432\u043E\u0434\u0430. +required.password=\u041F\u0430\u0440\u043E\u043B\u044C - \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435 \u043F\u043E\u043B\u0435 \u0432\u0432\u043E\u0434\u0430. + +# Authentication failure messages +authenticationFailure.AccountDisabledException=\u042D\u0442\u0430 \u0443\u0447\u0451\u0442\u043D\u0430\u044F \u0437\u0430\u043F\u0438\u0441\u044C \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E \u043D\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0443\u044E\u0449\u0430\u044F. +authenticationFailure.AccountLockedException=\u042D\u0442\u0430 \u0443\u0447\u0451\u0442\u043D\u0430\u044F \u0437\u0430\u043F\u0438\u0441\u044C \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D\u0430. +authenticationFailure.CredentialExpiredException=\u0412\u0430\u0448 \u043F\u0430\u0440\u043E\u043B\u044C \u043F\u0440\u043E\u0441\u0440\u043E\u0447\u0435\u043D. +authenticationFailure.InvalidLoginLocationException=\u0412\u0445\u043E\u0434 \u0432 \u0441\u0438\u0441\u0442\u0435\u043C\u0443 \u0438\u0437 \u0434\u0430\u043D\u043D\u043E\u0433\u043E \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0430 \u0437\u0430\u043F\u0440\u0435\u0449\u0451\u043D. +authenticationFailure.InvalidLoginTimeException=\u0412\u0445\u043E\u0434 \u0432 \u0441\u0438\u0441\u0442\u0435\u043C\u0443 \u0432 \u0434\u0430\u043D\u043D\u043E\u0435 \u0432\u0440\u0435\u043C\u044F \u0441\u0443\u0442\u043E\u043A \u0437\u0430\u043F\u0440\u0435\u0449\u0451\u043D. +authenticationFailure.AccountNotFoundException=\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0435 \u0434\u0430\u043D\u043D\u044B\u0435 \u043E \u043F\u043E\u0434\u043B\u0438\u043D\u043D\u043E\u0441\u0442\u0438. +authenticationFailure.FailedLoginException=\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0435 \u0434\u0430\u043D\u043D\u044B\u0435 \u043E \u043F\u043E\u0434\u043B\u0438\u043D\u043D\u043E\u0441\u0442\u0438. +authenticationFailure.UNKNOWN=\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0435 \u0434\u0430\u043D\u043D\u044B\u0435 \u043E \u043F\u043E\u0434\u043B\u0438\u043D\u043D\u043E\u0441\u0442\u0438. + +INVALID_REQUEST_PROXY=\u041E\u0431\u0430 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u0430 'pgt' \u0438 'targetService' \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u044B. +INVALID_TICKET_SPEC="Ticket" \u043D\u0435 \u043F\u0440\u043E\u0448\u0435\u043B \u0443\u0441\u043F\u0435\u0448\u043D\u0443\u044E \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u0438. \u0412\u043E\u0437\u043C\u043E\u0436\u043D\u044B\u0435 \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u0438 \u043E\u0448\u0438\u0431\u043E\u043A \u043C\u043E\u0433\u0443\u0442 \u0432\u043A\u043B\u044E\u0447\u0430\u0442\u044C \u043F\u043E\u043F\u044B\u0442\u043A\u0443 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0434\u0435\u0438\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u0438 "Proxy Ticket" \u043F\u043E\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u043E\u043C "Service Ticket validator" \u0438\u043B\u0438 \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 \u0438\u0441\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u044F \u0441 \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u0435\u043C "renew request: true". +INVALID_REQUEST=\u041E\u0431\u0430 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u0430 'service' \u0438 'ticket' \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u044B. +INVALID_TICKET="Ticket" ''{0}'' \u043D\u0435 \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u043D. +INVALID_SERVICE="Ticket" ''{0}'' \u043D\u0435 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u043F\u0440\u0435\u0434\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043D\u043E\u043C\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u0443. \u0418\u0437\u043D\u0430\u0447\u0430\u043B\u044C\u043D\u044B\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0431\u044B\u043B ''{1}'' , \u0430 \u043F\u0440\u0435\u0434\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043D\u044B\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0431\u044B\u043B ''{2}'' +INVALID_PROXY_CALLBACK=\u041F\u0440\u0435\u0434\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043D\u044B\u0439 proxy callback url ''{0}'' \u043D\u0435 \u043C\u043E\u0436\u0435\u0442 \u043F\u0440\u043E\u0439\u0442\u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 \u043F\u043E\u0434\u043B\u0438\u043D\u043D\u043E\u0441\u0442\u0438. +UNAUTHORIZED_SERVICE_PROXY=\u041F\u0440\u0435\u0434\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043D\u044B\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 ''{0}'' \u043D\u0435 \u0443\u043F\u043E\u043B\u043D\u043E\u043C\u043E\u0447\u0435\u043D \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C CAS proxy authentication. + +screen.service.error.header=\u041F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u043D\u0435 \u0443\u043F\u043E\u043B\u043D\u043E\u043C\u043E\u0447\u0435\u043D\u043E \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C CAS +screen.service.error.message=\u041F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435, \u043A\u043E\u0442\u043E\u0440\u043E\u0435 \u043F\u044B\u0442\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0439\u0442\u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 \u043F\u043E\u0434\u043B\u0438\u043D\u043D\u043E\u0441\u0442\u0438, \u043D\u0435 \u0443\u043F\u043E\u043B\u043D\u043E\u043C\u043E\u0447\u0435\u043D\u043E \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C CAS. +screen.service.empty.error.message=\u0420\u0435\u0433\u0438\u0441\u0442\u0440 \u0441\u0435\u0440\u0432\u0438\u0441\u043E\u0432 \u043F\u0443\u0441\u0442. \u041F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F, \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u044E\u0449\u0438\u0435 CAS, \u0434\u043E\u043B\u0436\u043D\u044B \u0431\u044B\u0442\u044C \u0432\u0432\u0435\u0434\u0435\u043D\u044B \u0432 \u0440\u0435\u0433\u0438\u0441\u0442\u0440 \u0441\u0435\u0440\u0432\u0438\u0441\u043E\u0432. + +# Password policy +password.expiration.warning=\u0421\u0440\u043E\u043A \u0433\u043E\u0434\u043D\u043E\u0441\u0442\u0438 \u043F\u0430\u0440\u043E\u043B\u044F \u0438\u0441\u0442\u0435\u043A\u0430\u0435\u0442 \u0447\u0435\u0440\u0435\u0437 {0} \u0434\u0435\u043D\u044C/\u0434\u043D\u0435\u0439. \u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430 \u0438\u0437\u043C\u0435\u043D\u0438\u0442\u0435 \u0441\u0432\u043E\u0439 \u043F\u0430\u0440\u043E\u043B\u044C \u0441\u0435\u0439\u0447\u0430\u0441. +password.expiration.loginsRemaining=\u0423 \u0432\u0430\u0441 \u043E\u0441\u0442\u0430\u043B\u043E\u0441\u044C {0} \u0432\u0445\u043E\u0434/\u0432\u0445\u043E\u0434\u043E\u0432 \u0432 \u0441\u0438\u0441\u0442\u0435\u043C\u0443 \u043F\u0435\u0440\u0435\u0434 \u0442\u0435\u043C \u043A\u0430\u043A \u0432\u044B \u0431\u0443\u0434\u0435\u0442\u0435 \u041E\u0411\u042F\u0417\u0410\u041D\u042B \u043F\u043E\u043C\u0435\u043D\u044F\u0442\u044C \u043F\u0430\u0440\u043E\u043B\u044C. +screen.accountdisabled.heading=\u042D\u0442\u0430 \u0443\u0447\u0451\u0442\u043D\u0430\u044F \u0437\u0430\u043F\u0438\u0441\u044C \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E \u043D\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0443\u044E\u0449\u0430\u044F. +screen.accountdisabled.message=\u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430 \u0441\u0432\u044F\u0436\u0438\u0442\u0435\u0441\u044C \u0441 \u0441\u0438\u0441\u0442\u0435\u043C\u043D\u044B\u043C \u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u043E\u043C \u0434\u043B\u044F \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u044F \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043C\u0443. +screen.accountlocked.heading=\u042D\u0442\u0430 \u0443\u0447\u0451\u0442\u043D\u0430\u044F \u0437\u0430\u043F\u0438\u0441\u044C \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D\u0430. +screen.accountlocked.message=\u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430 \u0441\u0432\u044F\u0436\u0438\u0442\u0435\u0441\u044C \u0441 \u0441\u0438\u0441\u0442\u0435\u043C\u043D\u044B\u043C \u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u043E\u043C \u0434\u043B\u044F \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u044F \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043C\u0443. +screen.expiredpass.heading=\u0421\u0440\u043E\u043A \u0433\u043E\u0434\u043D\u043E\u0441\u0442\u0438 \u0432\u0430\u0448\u0435\u0433\u043E \u043F\u0430\u0440\u043E\u043B\u044F \u043F\u0440\u043E\u0441\u0440\u043E\u0447\u0435\u043D. +screen.expiredpass.message=\u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430 \u043F\u043E\u043C\u0435\u043D\u044F\u0439\u0442\u0435 \u0432\u0430\u0448 \u043F\u0430\u0440\u043E\u043B\u044C. +screen.mustchangepass.heading=\u0412\u0430\u043C \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u043F\u043E\u043C\u0435\u043D\u044F\u0442\u044C \u0432\u0430\u0448 \u043F\u0430\u0440\u043E\u043B\u044C. +screen.mustchangepass.message=\u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430 \u043F\u043E\u043C\u0435\u043D\u044F\u0439\u0442\u0435 \u0432\u0430\u0448 \u043F\u0430\u0440\u043E\u043B\u044C. +screen.badhours.heading=\u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0435\u0442\u043D\u043E\u0439 \u0437\u0430\u043F\u0438\u0441\u0438 \u0437\u0430\u043F\u0440\u0435\u0449\u0451\u043D \u0432\u0445\u043E\u0434 \u0432 \u0441\u0438\u0441\u0442\u0435\u043C\u0443 \u0432 \u0434\u0430\u043D\u043D\u043E\u0435 \u0432\u0440\u0435\u043C\u044F \u0441\u0443\u0442\u043E\u043A. +screen.badhours.message=\u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430 \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u0432\u043E\u0439\u0442\u0438 \u0432 \u0441\u0438\u0441\u0442\u0435\u043C\u0443 \u043F\u043E\u0437\u0436\u0435. +screen.badworkstation.heading=\u0412\u044B \u043D\u0435 \u043C\u043E\u0436\u0435\u0442\u0435 \u0432\u043E\u0439\u0442\u0438 \u0432 \u0441\u0438\u0441\u0442\u0435\u043C\u0443 \u0441 \u0434\u0430\u043D\u043D\u043E\u0433\u043E \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0430. +screen.badworkstation.message=\u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430 \u0441\u0432\u044F\u0436\u0438\u0442\u0435\u0441\u044C \u0441 \u0441\u0438\u0441\u0442\u0435\u043C\u043D\u044B\u043C \u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u043E\u043C \u0434\u043B\u044F \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u044F \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043C\u0443. + +# OAuth +screen.oauth.confirm.header=\u0410\u0432\u0442\u043E\u0440\u0438\u0437\u0430\u0446\u0438\u044F +screen.oauth.confirm.message=\u0412\u044B \u043D\u0430\u043C\u0435\u0440\u0435\u043D\u044B \u0434\u0430\u0442\u044C \u043F\u043E\u043B\u043D\u044B\u0439 \u0434\u043E\u0441\u0442\u0443\u043F \u043A \u0432\u0430\u0448\u0435\u043C\u0443 \u043F\u0440\u043E\u0444\u0438\u043B\u044E \u0434\u0430\u043D\u043D\u043E\u043C\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u0443: "{0}" ? +screen.oauth.confirm.allow=\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044C \u0434\u043E\u0441\u0442\u0443\u043F + +# Unavailable +screen.unavailable.heading=CAS \u043D\u0435 \u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D +screen.unavailable.message=\u041F\u0440\u043E\u0438\u0437\u043E\u0448\u043B\u0430 \u043E\u0448\u0438\u0431\u043A\u0430 \u0432 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0435 \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u043A\u0438 \u0432\u0430\u0448\u0435\u0433\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0430. \u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430 \u0441\u0432\u044F\u0436\u0438\u0442\u0435\u0441\u044C \u0441 \u0432\u0430\u0448\u0435\u0439 \u0441\u043B\u0443\u0436\u0431\u043E\u0439 \u0442\u0435\u0445\u043D\u0438\u0447\u0435\u0441\u043A\u043E\u0439 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438 \u0438\u043B\u0438 \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451 \u0440\u0430\u0437. diff --git a/cas-server-webapp/src/main/resources/messages_sl.properties b/cas-server-webapp/src/main/resources/messages_sl.properties new file mode 100644 index 000000000000..afa088666922 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_sl.properties @@ -0,0 +1,63 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=Dobrodo\u0161li v ARNES CAS online\! Uporabite uporabni\u0161ko ime in geslo, ki vam ga je dodeli administrator ARNES organizacije +screen.welcome.security=Zaradi varnostnih razlogov, prosimo, da naredite odjavo in zaprete brskalnik, ko zapustite spletni vir, ki je zahteval va\u0161o avtentikacijo. +screen.welcome.instructions=Vpi\u0161ite va\u0161o uporabni\u0161ko ime(eduprincipalName\: ime@arnes.si) in geslo. +screen.welcome.label.netid=eduPersonPrincipalName\: +screen.welcome.label.netid.accesskey=n +screen.welcome.label.password=Geslo\: +screen.welcome.label.password.accesskey=p +screen.welcome.label.warn=Opozori me, ko naredim novo prijavo v drugi spletni vir. +screen.welcome.label.warn.accesskey=w +screen.welcome.button.login=Prijava +screen.welcome.button.clear=ZBRI\u0160I + +#Confirmation Screen Messages +screen.confirmation.message=Klikni tukaj za vstop v aplikacijo. + +#Generic Success Screen Messages +screen.success.header=Prijava uspela +screen.success.success=Uspe\u0161no ste se prijavili v Centralno Avtenikacijsko Storitev. +screen.success.security=Zaradi varnostnih razlogov, prosimo, da naredite odjavo in zaprete brskalnik, ko zapustite spletni vir, ki je zahteval va\u0161o avtentikacijo. + +#Logout Screen Messages +screen.logout.header=Odjava uspela +screen.logout.success=Uspe\u0161no ste se prijavili v Centralno Avtenikacijsko Storitev. +screen.logout.security=Zaradi varnostnih razlogov zaprite brskalnik +screen.logout.redirect=Spletna storitev iz katere ste se odjavili, je priskrbela povezavo za nazaj, \u010De se \u017Eelite vrniti, kliknite na povezavo.. + +#Service Error Messages +screen.service.error.header=Ne avtorizerana Storitev +screen.service.error.message=Vstopiti ste hoteli do o spletne storitve nima dovoljenja do uporabe CAS storitve. + + +error.invalid.loginticket=Ne morete narediti re-submit forme, ki je \u017Ee bila poslana. +required.username=Uporabni\u0161ko ime je nujno vpisati\! +required.password=Geslo je nujno vpisati\! +error.authentication.credentials.bad=Veredostojnost, ki ste jo vpisali ne moremo dolo\u010Diti, da je pristno\! +error.authentication.credentials.unsupported=Veredostojnost, ki ste jo vpisali ni podprto v CAS-u\! + +INVALID_REQUEST_PROXY='pgt' in 'targetService' parametra sta oba nujna\! +INVALID_TICKET_SPEC=Ne uspe\u0161na validacija zahtevka. Mo\u017Ene napake so nastale pri vklju\u010Ditvi validacije v Proxy Ticket preko Service Ticket validacije. +INVALID_REQUEST='service' in 'ticket' parametra sta oba nujna\! +INVALID_TICKET=zahtevek ''{0}'' ni prepoznana +INVALID_SERVICE=zahtevek ''{0}'' se ne ujema priskrbljeno storitvijo diff --git a/cas-server-webapp/src/main/resources/messages_sv.properties b/cas-server-webapp/src/main/resources/messages_sv.properties new file mode 100644 index 000000000000..1918c56774cd --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_sv.properties @@ -0,0 +1,68 @@ +#Author: Fredrik Nilsson http://www.infoflexconnect.se +#Updated 2006-08-29: PÃ¥l Axelsson & Veronika Berglund IT Support Department at Uppsala University http://www.uu.se +#Updated 2007-06-21: PÃ¥l Axelsson IT Support Department at Uppsala University http://www.uu.se + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=Välkommen till den centrala autentiseringstjänsten CAS. När du installerat men ännu inte konfigurerat CAS kan du autentisera genom att ange samma text som bÃ¥de användaridentitet och lösenord för att prova CAS. +screen.welcome.security=Av säkerhetsskäl bör du logga ut och stänga webbläsaren när du är färdig med webbtjänsterna som kräver inloggning. +screen.welcome.instructions=Ange din användaridentitet och ditt lösenord. +screen.welcome.label.netid=Användarid: +screen.welcome.label.netid.accesskey=a +screen.welcome.label.password=Lösenord: +screen.welcome.label.password.accesskey=l +screen.welcome.label.warn=Varna mig innan jag loggar pÃ¥ en annan webbtjänst. +screen.welcome.label.warn.accesskey=v +screen.welcome.button.login=LOGGA IN +screen.welcome.button.clear=RENSA + +#Confirmation Screen Messages +screen.confirmation.message=Klicka här för att komma till webbtjänsten. + +#Generic Success Screen Messages +screen.success.header=Inloggningen lyckades +screen.success.success=Du har loggat in i den centrala autentiseringstjänsten CAS. +screen.success.security=Av säkerhetsskäl bör du logga ut och stänga webbläsaren när du är färdig med webbtjänsterna som kräver inloggning. + +#Logout Screen Messages +screen.logout.header=Du har loggat ut! +screen.logout.success=Du har loggat ut frÃ¥n den centrala autentiseringstjänsten CAS. +screen.logout.security=Av säkerhetsskäl bör du stänga din webbläsare. +screen.logout.redirect=Du kan logga in igen genom att klicka här. + +screen.service.sso.error.header=Du mÃ¥ste logga in igen för att använda denna webbtjänst +screen.service.sso.error.message=Du försökte använda en webbtjänst som kräver att du loggar in igen för att använda den. Logga in igen! + +error.invalid.loginticket=Du kan inte Ã¥teranvända ett webbformulär som redan har skickats in. +required.username=Användaridentitet är en obligatoriskt uppgift. +required.password=Lösenord är en obligatoriskt uppgift. +error.authentication.credentials.bad=Inloggningsuppgifterna du angav kunde inte valideras! +error.authentication.credentials.unsupported=Inloggningsuppgifterna du angav kan inte hanteras av CAS. + +INVALID_REQUEST_PROXY=BÃ¥de 'pgt' och 'targetService' är obligatoriska parametrar. +INVALID_TICKET_SPEC=Ticket-valideringen misslyckades. Möjliga fel skulle kunna vara att försöka validera en Proxy Ticket via en validator för Service Ticket, eller att en ny inloggning inte genomfördes trots begäran. +INVALID_REQUEST=BÃ¥de 'service' och 'ticket' är obligatoriska parametrar. +INVALID_TICKET=ticket ''{0}'' känns inte igen. +INVALID_SERVICE=ticket ''{0}'' överenstämmer inte med angiven webbtjänst. + +screen.service.error.header=Ej auktoriserad webbtjänst +screen.service.error.message=Webbtjänsten du försökter ansluta till är ej auktoriserad att använda den centrala autentiseringstjänsten CAS. diff --git a/cas-server-webapp/src/main/resources/messages_tr.properties b/cas-server-webapp/src/main/resources/messages_tr.properties new file mode 100644 index 000000000000..f3511fc81b6b --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_tr.properties @@ -0,0 +1,67 @@ +# Author : Mert Caliskan +# http://www.jroller.com/mert + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=Tebrikler!, CAS'\u0131 \u00e7al\u0131\u015f\u0131r hale getirdiniz. Haz\u0131rdaki kimliklendirme mekanizmas\u0131 kullan\u0131c\u0131 ad\u0131 ve parola ayn\u0131 oldu\u011fu durumlarda giri\u015fe izin vermektedir. Hemen deneyebilirsiniz. +screen.welcome.security=G\u00fcvenli\u011finiz i\u00e7in, i\u015finiz bittikten sonra kulland\u0131\u011f\u0131n\u0131z uygulamalardan \u00e7\u0131k\u0131\u015f yap\u0131n\u0131z ve taray\u0131c\u0131n\u0131z\u0131 kapat\u0131n\u0131z. +screen.welcome.instructions=Kullan\u0131c\u0131 ad\u0131 ve parolan\u0131z\u0131 giriniz +screen.welcome.label.netid=Kullan\u0131c\u0131 Ad\u0131: +screen.welcome.label.netid.accesskey=k +screen.welcome.label.password=Parola: +screen.welcome.label.password.accesskey=p +screen.welcome.label.warn=Di\u011fer sitelere girmeden \u00f6nce beni uyar. +screen.welcome.label.warn.accesskey=u +screen.welcome.button.login=G\u0130R\u0130\u015e +screen.welcome.button.clear=TEM\u0130ZLE + +#Confirmation Screen Messages +screen.confirmation.message=Uygulamaya eri\u015fmek i\u00e7in buraya t\u0131klay\u0131n\u0131z. + +#Generic Success Screen Messages +screen.success.header=Oturum ba\u015far\u0131yla a\u00e7\u0131ld\u0131. +screen.success.success=Merkezi Kimliklendirme Servisi'ne ba\u015far\u0131l\u0131 bir \u015fekilde giri\u015f yapt\u0131n\u0131z. +screen.success.security=G\u00fcvenlik nedenlerinden dolay\u0131, uygulamalar\u0131n kullan\u0131m\u0131 bittikten sonra sistemden \u00e7\u0131k\u0131\u015f yap\u0131p, taray\u0131c\u0131n\u0131z\u0131 kapat\u0131n\u0131z. + +#Logout Screen Messages +screen.logout.header=Oturum ba\u015far\u0131yla kapat\u0131ld\u0131. +screen.logout.success=Merkezi Kimliklendirme Servisi'nden ba\u015far\u0131l\u0131 bir \u015fekilde \u00e7\u0131k\u0131\u015f yapt\u0131n\u0131z. +screen.logout.security=G\u00fcvenlik nedenlerinden dolay\u0131, taray\u0131c\u0131n\u0131z\u0131 kapan\u0131t\u0131z. +screen.logout.redirect=Kimliklendirme servisi'ne y\u00f6nlendirme i\u00e7in verilen ba\u011flant\u0131ya t\u0131klayarak devam edebilirsiniz. + +screen.service.sso.error.header=Bu servise eri\u015fim i\u00e7in tekrar kimliklendirme gerekmektedir. +screen.service.sso.error.message=Bir servise ard\u0131\u015f\u0131k kimlik onay\u0131 yaparak eri\u015fmeye \u00e7al\u0131\u015ft\u0131n\u0131z. Onay i\u00e7in l\u00fctfen tekrar t\u0131klay\u0131n\u0131z. + +error.invalid.loginticket=\u00d6nceden g\u00f6nderilmi\u015f bir giri\u015f formunu tekrar g\u00f6nderemezsiniz. +required.username=Kullan\u0131c\u0131 Ad\u0131 girilmesi gerekli bir aland\u0131r. +required.password=Parola girilmesi gerekli bir aland\u0131r. +error.authentication.credentials.bad=Kullan\u0131c\u0131 Kodu veya Parola bilginizde yanl\u0131\u015fl\u0131k var. L\u00fctfen kontrol edip tekrar deneyiniz. +error.authentication.credentials.unsupported=Sa\u011flad\u0131\u011f\u0131n\u0131z kimliklendirme bilgileri Merkezi Kimliklendirme Sistemi taraf\u0131ndan tan\u0131nmamaktad\u0131r. + +INVALID_REQUEST_PROXY='pgt' ve 'targetService' parametrelerinin her ikisi birden gereklidir. +INVALID_TICKET_SPEC=Bilet do\u011frulama ba\u015far\u0131s\u0131z oldu. Olas\u0131 hatalar, servis bilet do\u011frulay\u0131c\u0131 ile Vekil (Proxy) bilet do\u011frulamak veya do\u011fru yenileme iste\u011fi kural\u0131na uyulmamas\u0131 olabilir. +INVALID_REQUEST='service' ve 'ticket' parametrelerinin her ikisi birden gereklidir. +INVALID_TICKET=Tan\u0131ms\u0131z bilet: ''{0}'' +INVALID_SERVICE=Bilet ''{0}'' belirtilen servis ile e\u015fle\u015fmiyor. As\u0131l servis: ''{1}'', belirtilen servis: ''{2}''. + +screen.service.error.header=Uygulama, Merkezi Kimliklendirme Servisi'ni kullanmak i\u00e7in yetkilendirilmemi\u015f. +screen.service.error.message=Kimliklendirme onay\u0131 yap\u0131lmaya \u00e7al\u0131\u015f\u0131lan uygulama, Merkezi Kimliklendirme Servisi'ni kullanmak i\u00e7in yetkilendirilmemi\u015f. diff --git a/cas-server-webapp/src/main/resources/messages_uk.properties b/cas-server-webapp/src/main/resources/messages_uk.properties new file mode 100644 index 000000000000..91709330e0eb --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_uk.properties @@ -0,0 +1,102 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=\u0412\u0456\u0442\u0430\u0454\u043c\u043e \u0437 \u0443\u0441\u043f\u0456\u0448\u043d\u0438\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u043e\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u0438 CAS! "Authentication handler", \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439 \u0437\u0430 \u0443\u043c\u043e\u0432\u0447\u0430\u043d\u043d\u044f\u043c, \u0432\u0438\u0440\u043e\u0431\u043b\u044f\u0454 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0430\u0432\u0442\u0435\u043d\u0442\u0438\u0447\u043d\u043e\u0441\u0442\u0456 \u0432 \u0442\u043e\u043c\u0443 \u0432\u0438\u043f\u0430\u0434\u043a\u0443 \u044f\u043a\u0449\u043e \u0456\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430 \u0456 \u043f\u0430\u0440\u043e\u043b\u044c \u0441\u043f\u0456\u0432\u043f\u0430\u0434\u0430\u044e\u0442\u044c: \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 CAS \u0432 \u0434\u0456\u0457. +screen.welcome.security=\u0417 \u043c\u0435\u0442\u043e\u044e \u043d\u0430\u0434\u0456\u0439\u043d\u043e\u0433\u043e \u0440\u0456\u0432\u043d\u044f \u0431\u0435\u0437\u043f\u0435\u043a\u0438, \u0431\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0432\u0438\u0439\u0434\u0456\u0442\u044c \u0456\u0437 \u0441\u0438\u0441\u0442\u0435\u043c\u0438, \u0430 \u0442\u0430\u043a\u043e\u0436 \u0437\u0430\u043a\u0440\u0438\u0439\u0442\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440, \u0437\u0430\u043a\u0456\u043d\u0447\u0438\u0432\u0448\u0438 \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0441\u0435\u0440\u0432\u0456\u0441\u0443 \u044f\u043a\u0438\u0439 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0454 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0441\u043f\u0440\u0430\u0432\u0436\u043d\u043e\u0441\u0442\u0456! +screen.welcome.instructions=\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043b\u043e\u0433\u0456\u043d \u0456 \u043f\u0430\u0440\u043e\u043b\u044c. +screen.welcome.label.netid=\u041b\u043e\u0433\u0456\u043d: +screen.welcome.label.netid.accesskey=n +screen.welcome.label.password=\u041f\u0430\u0440\u043e\u043b\u044c: +screen.welcome.label.password.accesskey=p +screen.welcome.label.warn=\u041f\u043e\u043f\u0435\u0440\u0435\u0434\u0438\u0442\u0438 \u043f\u0435\u0440\u0435\u0434 \u0432\u0445\u043e\u0434\u043e\u043c \u043d\u0430 \u0456\u043d\u0448\u0456 \u0441\u0430\u0439\u0442\u0438. +screen.welcome.label.warn.accesskey=\u043f +screen.welcome.button.login=\u0423\u0412\u0406\u0419\u0422\u0418 +screen.welcome.button.clear=\u041e\u0427\u0418\u0421\u0422\u0418\u0422\u0418 + +#Confirmation Screen Messages +screen.confirmation.message=\u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u043d\u0430 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u0443 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443. + +#Generic Success Screen Messages +screen.success.header=\u0412\u0445\u0456\u0434 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0443\u0441\u043f\u0456\u0448\u043d\u0438\u0439. +screen.success.success=\u0412\u0438 \u0443\u0441\u043f\u0456\u0448\u043d\u043e \u0443\u0432\u0456\u0439\u0448\u043b\u0438 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 "Central Authentication Service". +screen.success.security=\u0417 \u043c\u0435\u0442\u043e\u044e \u043d\u0430\u0434\u0456\u0439\u043d\u043e\u0433\u043e \u0440\u0456\u0432\u043d\u044f \u0431\u0435\u0437\u043f\u0435\u043a\u0438, \u0431\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0432\u0438\u0439\u0434\u0456\u0442\u044c \u0456\u0437 \u0441\u0438\u0441\u0442\u0435\u043c\u0438, \u0430 \u0442\u0430\u043a\u043e\u0436 \u0437\u0430\u043a\u0440\u0438\u0439\u0442\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440, \u0437\u0430\u043a\u0456\u043d\u0447\u0438\u0432\u0448\u0438 \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0441\u0435\u0440\u0432\u0456\u0441\u0443 \u044f\u043a\u0438\u0439 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0454 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0441\u043f\u0440\u0430\u0432\u0436\u043d\u043e\u0441\u0442\u0456! + +#Logout Screen Messages +screen.logout.header=\u0412\u0438\u0445\u0456\u0434 \u0456\u0437 \u0441\u0438\u0441\u0442\u0435\u043c\u0438 \u0443\u0441\u043f\u0456\u0448\u043d\u0438\u0439. +screen.logout.success=\u0412\u0438 \u0443\u0441\u043f\u0456\u0448\u043d\u043e \u0432\u0438\u0439\u0448\u043b\u0438 \u0437 \u0441\u0438\u0441\u0442\u0435\u043c\u0438 "Central Authentication Service". +screen.logout.security=\u0417 \u043c\u0435\u0442\u043e\u044e \u043d\u0430\u0434\u0456\u0439\u043d\u043e\u0433\u043e \u0440\u0456\u0432\u043d\u044f \u0431\u0435\u0437\u043f\u0435\u043a\u0438, \u0437\u0430\u043a\u0440\u0438\u0439\u0442\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440. +screen.logout.redirect=\u0421\u0435\u0440\u0432\u0456\u0441, \u0434\u043e \u044f\u043a\u043e\u0433\u043e \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0438\u0439 \u0434\u043e\u0441\u0442\u0443\u043f, \u043d\u0430\u0434\u0430\u0432 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u0443. + +#Service Error Messages +screen.service.error.header=\u0421\u0435\u0440\u0432\u0456\u0441 \u0431\u0435\u0437 \u043f\u0440\u0430\u0432 \u0434\u043e\u0441\u0442\u0443\u043f\u0443. +screen.service.error.message=\u0421\u0435\u0440\u0432\u0456\u0441, \u0434\u043b\u044f \u044f\u043a\u043e\u0433\u043e \u0437\u0440\u043e\u0431\u043b\u0435\u043d\u0430 \u0441\u043f\u0440\u043e\u0431\u0430 \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0438 \u0430\u0432\u0442\u0435\u043d\u0442\u0438\u0447\u043d\u043e\u0441\u0442\u0456, \u043d\u0435 \u043c\u0430\u0454 \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u0434\u043e \u0441\u0438\u0441\u0442\u0435\u043c\u0438 CAS. + + +error.invalid.loginticket=\u0412\u0438 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u0456\u0434\u0456\u0441\u043b\u0430\u0442\u0438 \u0432\u0435\u0431-\u0444\u043e\u0440\u043c\u0443, \u044f\u043a\u0430 \u0432\u0436\u0435 \u0432\u0456\u0434\u0456\u0441\u043b\u0430\u043d\u0430. +required.username=\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430 - \u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u0435 \u043f\u043e\u043b\u0435 \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044f. +required.password=\u041f\u0430\u0440\u043e\u043b\u044c - \u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u0435 \u043f\u043e\u043b\u0435 \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044f. +error.authentication.credentials.bad=\u0421\u043f\u0440\u0430\u0432\u0436\u043d\u0456\u0441\u0442\u044c \u043d\u0430\u0434\u0430\u043d\u0438\u0445 \u0432\u0456\u0440\u0447\u0438\u0445 \u0434\u0430\u043d\u0438\u0445 \u043d\u0435 \u043c\u043e\u0436\u0435 \u0431\u0443\u0442\u0438 \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u0430. +error.authentication.credentials.unsupported=\u041d\u0430\u0434\u0430\u043d\u0456 \u0432\u0456\u0440\u0447\u0456 \u0434\u0430\u043d\u0456 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u044e\u0442\u044c\u0441\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u043e\u044e CAS. + +INVALID_REQUEST_PROXY=\u041e\u0431\u0438\u0434\u0432\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 'pgt' \u0456 'targetService' \u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u0456. +INVALID_TICKET_SPEC="Ticket" \u043d\u0435 \u043f\u0440\u043e\u0439\u0448\u043e\u0432 \u0443\u0441\u043f\u0456\u0448\u043d\u0443 \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0443 \u0434\u0456\u0439\u0441\u043d\u043e\u0441\u0442\u0456. \u041c\u043e\u0436\u043b\u0438\u0432\u0456 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 \u043f\u043e\u043c\u0438\u043b\u043e\u043a \u043c\u043e\u0436\u0443\u0442\u044c \u0432\u043a\u043b\u044e\u0447\u0430\u0442\u0438 \u0441\u043f\u0440\u043e\u0431\u0443 \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0438 \u0434\u0435\u0456\u0441\u0442\u0432\u0456\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0456 "Proxy Ticket" \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e "Service Ticket validator" \u0430\u0431\u043e \u043d\u0435 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u043d\u0456\u0441\u0442\u044c \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u043d\u044f \u0437 \u0432\u0438\u043c\u043e\u0433\u043e\u044e "renew request: true". +INVALID_REQUEST=\u041e\u0431\u0438\u0434\u0432\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 'service' \u0456 'ticket' \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b. +INVALID_TICKET="ticket" ''{0}'' \u043d\u0435 \u0440\u043e\u0437\u043f\u0456\u0437\u043d\u0430\u043d\u043e. +INVALID_SERVICE="ticket" ''{0}'' \u043d\u0435 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u0430\u0454 \u043d\u0430\u0434\u0430\u043d\u043e\u043c\u0443 \u0441\u0435\u0440\u0432\u0456\u0441\u0443. +management.services.link.logout=\u0412\u0438\u0439\u0442\u0438 +management.services.title=\u0423\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f \u0441\u0435\u0440\u0432\u0456\u0441\u0430\u043c\u0438 +management.services.status.deleted={0} \u0443\u0441\u043f\u0456\u0448\u043d\u043e \u0432\u0438\u0434\u0430\u043b\u0435\u043d\u0438\u0439. +management.services.service.warn=CAS \u043f\u0440\u0430\u0446\u044e\u0454 \u0432 "\u0432\u0456\u0434\u043a\u0440\u0438\u0442\u043e\u043c\u0443" \u0440\u0435\u0436\u0438\u043c\u0456, \u0442\u043e\u043c\u0443 \u0449\u043e \u0436\u043e\u0434\u0435\u043d \u0441\u0435\u0440\u0432\u0456\u0441 \u043d\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0438\u0439 \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u0446\u0456\u0454\u0457 \u0443\u0442\u0438\u043b\u0456\u0442\u0438. \u041f\u0456\u0441\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f, CAS \u0431\u0456\u043b\u044c\u0448 \u043d\u0435 \u0431\u0443\u0434\u0435 \u0444\u0443\u043d\u043a\u0446\u0456\u043e\u043d\u0443\u0432\u0430\u0442\u0438 \u0443 \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u043e\u043c\u0443 \u0440\u0435\u0436\u0438\u043c\u0456, \u0442\u043e\u043c\u0443 \u0432\u0430\u043c \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0431\u0443\u0434\u0435 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0432\u0430\u0442\u0438 \u0442\u0443\u0442 \u0443\u0441\u0456 \u0434\u043e\u0434\u0430\u0442\u043a\u0438, \u044f\u043a\u0456 \u0431\u0443\u0434\u0443\u0442\u044c \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 CAS \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457. \u042f\u043a\u0449\u043e \u0432\u0438 \u043f\u043b\u0430\u043d\u0443\u0454\u0442\u0435 \u0446\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438, \u0441\u043f\u043e\u0447\u0430\u0442\u043a\u0443 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0414\u041e\u0414\u0410\u0422\u0418 \u0421\u0410\u041c \u0421\u0415\u0420\u0412\u0406\u0421. URL \u0441\u0435\u0440\u0432\u0456\u0441\u0443 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f "{0}". +management.services.add.button.cancel=\u0421\u043a\u0430\u0441\u0443\u0432\u0430\u043d\u043d\u044f +management.services.add.button.save=\u0417\u0431\u0435\u0440\u0435\u0433\u0442\u0438 \u0437\u043c\u0456\u043d\u0438 +management.services.add.instructions=\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430 \u043f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043d\u0456 \u0447\u0438 \u0437\u043c\u0456\u043d\u0438, \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0432\u0448\u0438 \u043d\u0430 \u043a\u043d\u043e\u043f\u043a\u0443 "\u0417\u0431\u0435\u0440\u0435\u0433\u0442\u0438 \u0437\u043c\u0456\u043d\u0438" \u0432\u043d\u0438\u0437\u0443 \u0441\u0442\u043e\u0440\u0456\u043d\u043a\u0438 +management.services.add.property.attributes=\u0410\u0442\u0442\u0440\u0456\u0431\u0443\u0442\u0438 +management.services.add.property.description=\u041e\u043f\u0438\u0441 +management.services.add.property.evaluationOrder=\u041f\u043e\u0440\u044f\u0434\u043e\u043a +management.services.add.property.ignoreAttributes=\u041d\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u043a\u0435\u0440\u0443\u0432\u0430\u043d\u043d\u044f \u0430\u0442\u0442\u0440\u0456\u0431\u0443\u0442\u043e\u0432 +management.services.add.property.name=\u0406\u043c'\u044f +management.services.add.property.serviceUrl=URL \u0441\u0435\u0440\u0432\u0456\u0441\u0443 +management.services.add.property.serviceUrl.instructions=\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u0456 \u0432\u0438\u0440\u0430\u0437\u0438 \u0432 \u0441\u0442\u0438\u043b\u0456 Ant +management.services.add.property.status=\u0421\u0442\u0430\u0442\u0443\u0441 +management.services.add.property.status.allowedToProxy=\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u043f\u0440\u043e\u043a\u0441\u0456\u0440\u043e\u0432\u0430\u043d\u0456\u0435 +management.services.add.property.status.anonymousAccess=\u0410\u043d\u043e\u043d\u0456\u043c\u043d\u0438\u0439 \u0434\u043e\u0441\u0442\u0443\u043f +management.services.add.property.status.enabled=\u0410\u043a\u0442\u0438\u0432\u043d\u043e +management.services.add.property.status.ssoParticipant=\u0423\u0447\u0430\u0441\u043d\u0438\u043a SSO +management.services.add.property.themeName=\u0406\u043c'\u044f \u0442\u0435\u043c\u0438 +management.services.manage.action.delete=\u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438 +management.services.manage.action.edit=\u0440\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u0442\u0438 +management.services.manage.label.allowedToProxy=\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u043f\u0440\u043e\u043a\u0441\u0456\u0440\u043e\u0432\u0430\u043d\u0456\u0435 +management.services.manage.label.enabled=\u0412\u043a\u043b\u044e\u0447\u0435\u043d\u043e +management.services.manage.label.name=\u041d\u0430\u0437\u0432\u0430 \u0441\u0435\u0440\u0432\u0456\u0441\u0443 +management.services.manage.label.serviceUrl=URL \u0441\u0435\u0440\u0432\u0456\u0441\u0443 +management.services.manage.label.ssoParticipant=\u0423\u0447\u0430\u0441\u043d\u0438\u043a SSO +management.services.status.notdeleted=\u0421\u0435\u0440\u0432\u0456\u0441 \u043d\u0435 \u043c\u043e\u0436\u0435 \u0431\u0443\u0442\u0438 \u0432\u0438\u0434\u0430\u043b\u0435\u043d\u0438\u0439. +manageServiceView=\u0423\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f \u0441\u0435\u0440\u0432\u0456\u0441\u0430\u043c\u0438 +registeredService.serviceId.exists=\u0421\u0435\u0440\u0432\u0456\u0441 \u0437 \u0442\u0430\u043a\u0438\u043c URL \u0432\u0436\u0435 \u0456\u0441\u043d\u0443\u0454 +screen.blocked.header=\u0414\u043e\u0441\u0442\u0443\u043f \u0437\u0430\u0431\u043e\u0440\u043e\u043d\u0435\u043d\u043e +screen.blocked.message=\u0412\u0438 \u0437\u0430\u043d\u0430\u0434\u0442\u043e \u0431\u0430\u0433\u0430\u0442\u043e \u0440\u0430\u0437\u0456\u0432 \u0432\u0432\u043e\u0434\u0438\u043b\u0438 \u043d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043f\u0430\u0440\u043e\u043b\u044c. \u0412\u0430\u0448 \u0430\u043a\u0430\u0443\u043d\u0442 \u0437\u0430\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u0438\u0439. +screen.service.sso.error.header=\u0414\u043b\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u0434\u043e \u0441\u0435\u0440\u0432\u0456\u0441\u0443 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u044f +screen.service.sso.error.message=\u0412\u0438 \u043d\u0430\u043c\u0430\u0433\u0430\u0454\u0442\u0435\u0441\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0441\u0435\u0440\u0432\u0456\u0441\u0443, \u044f\u043a\u0438\u0439 \u0432\u0438\u043c\u0430\u0433\u0430\u0454 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u044e \u0431\u0435\u0437 \u043f\u043e\u0432\u0442\u043e\u0440\u0456\u0432 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457. \u0421\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u0437\u043d\u043e\u0432\u0443. +addServiceView=\u0414\u043e\u0434\u0430\u0442\u0438 \u043d\u043e\u0432\u0438\u0439 \u0441\u0435\u0440\u0432\u0456\u0441 +application.errors.global=\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0432\u0438\u043f\u0440\u0430\u0432\u0442\u0435 \u043d\u0430\u0432\u0435\u0434\u0435\u043d\u0456 \u043d\u0438\u0436\u0447\u0435 \u043f\u043e\u043c\u0438\u043b\u043a\u0438 +application.title=Jasig Central Authentication Service +editServiceView=\u0420\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u0442\u0438 \u0441\u0435\u0440\u0432\u0456\u0441 +viewStatisticsView=\u041f\u043e\u0434\u0438\u0432\u0438\u0442\u0438\u0441\u044f \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0443 diff --git a/cas-server-webapp/src/main/resources/messages_ur.properties b/cas-server-webapp/src/main/resources/messages_ur.properties new file mode 100644 index 000000000000..c6cbea47dfb8 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_ur.properties @@ -0,0 +1,66 @@ +#Author: Faizan Ahmed (Rutgers University) +#Since 3.0.5 + +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=CAS ko online lany par Mubark baad! Default Tasdeek karney wala aap ki tasdeek iss soorat main karay ga agar password wo hi hoo jo user name hay. Aiye, aur try ki jiyay. +screen.welcome.security=Security ki wajoohat ki bina par aap mehrbani farma kar apnay web browser say Log Out aur Exit zaroor ki jiyay jub aap aisi services isstamal kar chookay hoon jo tasdeek chahti hoon. +screen.welcome.instructions=Apni Apereo ki NetID aur Password enter ki jiyay. +screen.welcome.label.netid=NetID: +screen.welcome.label.netid.accesskey=n +screen.welcome.label.password=Password: +screen.welcome.label.password.accesskey=p +screen.welcome.label.warn=Mujay doosri sites main login karnay say pahlay Khabardar karain. +screen.welcome.label.warn.accesskey=k +screen.welcome.button.login=LOGIN +screen.welcome.button.clear=CLEAR + +#Confirmation Screen Messages +screen.confirmation.message=Yahan Click karain agar app application main dakhil hona chahtay hain. + +#Generic Success Screen Messages +screen.success.header=Log In Kamyab +screen.success.success=Aap kamyabi say Centeral Authentication Service main login hoo chokay hain. +screen.success.security=Security ki wajoohat ki bina par jub aap aisi services isstamal kar chookay hoon jo tasdeek chahti hoon tou baraye mehrbani apnay web browser say Log Out aur Exit zaroor ki jiyay + +#Logout Screen Messages +screen.logout.header=Logout Kamyab +screen.logout.success=Aap kamyabi say Centeral Authentication Service say logout hoo chokay hain. +screen.logout.security=Security ki wajoohat ki bina par apnay web browser say exit karain. +screen.logout.redirect=Aap jis service say aye hain oos nay aik link supply kia hay jissay aap agar chahain tou follow kar saktay hain. + + +#Service Error Messages +screen.service.error.header=Bay Sanud Service +screen.service.error.message=Aap jiss service kay liay tasdeek ki kooshush kar rahay thay woo service CAS istamal karnay ki mijaz nahi. + +error.invalid.loginticket=Aap oos form ko dobara arsaal karnay ki kooshsish nahi kar saktay joo aap pahly arsal kar chookay hoon. +required.username=Username ka khana por karna lazmi hay. +required.password=Password ka khana por karna lazmi hay. +error.authentication.credentials.bad=Aap ka mohya kia howa waseeka (parteet puter) ki tasdeek karna momkin nahi. +error.authentication.credentials.unsupported=Aap kay mohya kiay howay waseeka (parteet puter) ko CAS support nahi karta. + +INVALID_REQUEST_PROXY='pgt' aur 'targetService' parameters doonon lazmi hain. +INVALID_TICKET_SPEC=Ticket toseek ki tasreeh par poora nahi utri. Momkin gultiyoon main shamil, hoo sakta hay kay proxy ticket ki toseek ki kooshish Service ticket kay toseek kaninda say ki gai hoo, yaa 'renew true request' say iss ki mitabkat na hooti hoo. +INVALID_REQUEST='service' aur 'ticket' parameters doonon lazmi hain. +INVALID_TICKET=ticket ''{0}'' ki shnakhat nahi hoo saki. +INVALID_SERVICE=ticket ''{0}'' ki mitabkat mohya karda service say nahi hoo saki. diff --git a/cas-server-webapp/src/main/resources/messages_zh_CN.properties b/cas-server-webapp/src/main/resources/messages_zh_CN.properties new file mode 100644 index 000000000000..28427bf8b668 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_zh_CN.properties @@ -0,0 +1,108 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=\u6b22\u8fce\u6765\u5230\u4e2d\u592e\u8ba4\u8bc1\u7cfb\u7edf\u3002\u9ed8\u8ba4\u7684\u8ba4\u8bc1\u5904\u7406\u5668\u652f\u6301\u90a3\u4e9b\u7528\u6237\u540d\u7b49\u4e8e\u5bc6\u7801\u7684\u8d26\u53f7\uff0c\u5f00\u53d1\u8005\u53ef\u4ee5\u8bd5\u8bd5\u770b\u3002 +screen.welcome.security=\u51fa\u4e8e\u5b89\u5168\u8003\u8651\uff0c\u4e00\u65e6\u60a8\u8bbf\u95ee\u8fc7\u90a3\u4e9b\u9700\u8981\u60a8\u63d0\u4f9b\u51ed\u8bc1\u4fe1\u606f\u7684\u5e94\u7528\u65f6\uff0c\u8bf7\u64cd\u4f5c\u5b8c\u6210\u4e4b\u540e\u5173\u95ed\u6d4f\u89c8\u5668\u3002 +screen.welcome.instructions=\u8bf7\u8f93\u5165\u60a8\u7684\u7528\u6237\u540d\u548c\u5bc6\u7801. +screen.welcome.label.netid=\u7528\u6237\u540d: +screen.welcome.label.netid.accesskey=n +screen.welcome.label.password=\u5bc6\u3000\u7801: +screen.welcome.label.password.accesskey=p +screen.welcome.label.warn=\u8f6c\u5411\u5176\u4ed6\u7ad9\u70b9\u524d\u63d0\u793a\u6211\u3002 +screen.welcome.label.warn.accesskey=w +screen.welcome.button.login=\u767b\u5f55 +screen.welcome.button.clear=\u91cd\u7f6e + +logo.title=\u8f6c\u5230Apereo\u7f51\u7ad9\u9996\u9875 +copyright=\u7248\u6743\u6240\u6709 © 2005–2012 Apereo, Inc. \u4fdd\u7559\u5168\u90e8\u6743\u5229\u3002 + +# Blocked Errors Page +screen.blocked.header=\u8bbf\u95ee\u88ab\u62d2\u7edd +screen.blocked.message=\u8f93\u9519\u5bc6\u7801\u6b21\u6570\u592a\u591a\uff0c\u8d26\u53f7\u88ab\u9501\u5b9a\u3002 + +#Confirmation Screen Messages +screen.confirmation.message=\u5355\u51fb \u8fd9\u91cc \uff0c\u4fbf\u80fd\u591f\u8bbf\u95ee\u5230\u76ee\u6807\u5e94\u7528\u3002 + +#Generic Success Screen Messages +screen.success.header=\u767b\u5f55\u6210\u529f +screen.success.success=\u60a8\u5df2\u7ecf\u6210\u529f\u767b\u5f55\u4e2d\u592e\u8ba4\u8bc1\u7cfb\u7edf\u3002 +screen.success.security=\u51fa\u4e8e\u5b89\u5168\u8003\u8651\uff0c\u4e00\u65e6\u60a8\u8bbf\u95ee\u8fc7\u90a3\u4e9b\u9700\u8981\u60a8\u63d0\u4f9b\u51ed\u8bc1\u4fe1\u606f\u7684\u5e94\u7528\u65f6\uff0c\u8bf7\u64cd\u4f5c\u5b8c\u6210\u4e4b\u540e\u5173\u95ed\u6d4f\u89c8\u5668\u3002 + +#Logout Screen Messages +screen.logout.header=\u6ce8\u9500\u6210\u529f +screen.logout.success=\u60a8\u5df2\u7ecf\u6210\u529f\u9000\u51faCAS\u7cfb\u7edf\uff0c\u8c22\u8c22\u4f7f\u7528\uff01 +screen.logout.security=\u51fa\u4e8e\u5b89\u5168\u8003\u8651\uff0c\u8bf7\u5173\u95ed\u60a8\u7684\u6d4f\u89c8\u5668\u3002 +screen.logout.redirect=\u60a8\u53ef\u4ee5\u901a\u8fc7\u5982\u4e0bURL\u8bbf\u95ee\u5230\u76ee\u6807\u670d\u52a1\uff1a\u76ee\u6807\u670d\u52a1. + +screen.service.sso.error.header=\u5728\u8bbf\u95ee\u5230\u5230\u76ee\u6807\u670d\u52a1\u524d\uff0c\u4f60\u5fc5\u987b\u7ecf\u8fc7\u91cd\u65b0\u8ba4\u8bc1\u7684\u8003\u9a8c +screen.service.sso.error.message=\u4f60\u6b63\u8bd5\u56fe\u8bbf\u95ee\u8981\u6c42\u91cd\u65b0\u8ba4\u8bc1\u7684\u670d\u52a1\u3002\u8bf7\u5c1d\u8bd5\u8fdb\u884c\u518d\u6b21\u8ba4\u8bc1\u3002 + +error.invalid.loginticket=\u60a8\u4e0d\u80fd\u591f\u518d\u6b21\u63d0\u4ea4\u5df2\u7ecf\u63d0\u4ea4\u8fc7\u7684\u8868\u5355\u3002 +required.username=\u5fc5\u987b\u5f55\u5165\u7528\u6237\u540d\u3002 +required.password=\u5fc5\u987b\u5f55\u5165\u5bc6\u7801\u3002 + +# Authentication failure messages +authenticationFailure.AccountDisabledException=\u8fd9\u4e2a\u8d26\u6237\u88ab\u7981\u7528\u4e86\u3002 +authenticationFailure.AccountLockedException=\u8fd9\u4e2a\u8d26\u6237\u88ab\u4e0a\u9501\u4e86\u3002 +authenticationFailure.CredentialExpiredException=\u4f60\u7684\u5bc6\u7801\u8fc7\u671f\u4e86\u3002 +authenticationFailure.InvalidLoginLocationException=\u4f60\u4e0d\u80fd\u4ece\u8fd9\u4e2a\u5de5\u4f5c\u7ad9\u767b\u5f55\u3002 +authenticationFailure.InvalidLoginTimeException=\u4f60\u7684\u8d26\u6237\u73b0\u5728\u88ab\u7981\u6b62\u767b\u5f55\u4e86\u3002 +authenticationFailure.AccountNotFoundException=\u8ba4\u8bc1\u4fe1\u606f\u65e0\u6548\u3002 +authenticationFailure.FailedLoginException=\u8ba4\u8bc1\u4fe1\u606f\u65e0\u6548\u3002 +authenticationFailure.UNKNOWN=\u8ba4\u8bc1\u4fe1\u606f\u65e0\u6548\u3002 + +INVALID_REQUEST_PROXY=\u5fc5\u987b\u540c\u65f6\u63d0\u4f9b'pgt'\u548c'targetService'\u53c2\u6570 +INVALID_TICKET_SPEC=\u6821\u9a8c\u7968\u6839\u5931\u8d25\u3002\u60a8\u53ef\u80fd\u91c7\u7528\u670d\u52a1\u7968\u6839\u6765\u6821\u9a8c\u4ee3\u7406\u7968\u6839\uff0c\u6216\u6ca1\u6709\u5c06renew\u8bbe\u4e3atrue\u3002 +INVALID_REQUEST=\u5fc5\u987b\u540c\u65f6\u63d0\u4f9b'service'\u548c'ticket'\u53c2\u6570 +INVALID_TICKET=\u672a\u80fd\u591f\u8bc6\u522b\u51fa\u76ee\u6807 ''{0}''\u7968\u6839 +INVALID_SERVICE=\u7968\u6839''{0}''\u4e0d\u7b26\u5408\u76ee\u6807\u670d\u52a1 +INVALID_PROXY_CALLBACK=\u6240\u63d0\u4f9b\u7684\u4ee3\u7406\u56de\u8c03\u7f51\u5740''{0}''\u4e0d\u80fd\u63d0\u4f9b\u8ba4\u8bc1\u3002 +UNAUTHORIZED_SERVICE_PROXY=\u6240\u63d0\u4f9b\u7684\u670d\u52a1''{0}''\u6ca1\u6709\u6743\u9650\u4f7f\u7528CAS\u4ee3\u7406\u7684\u8ba4\u8bc1\u65b9\u5f0f\u3002 + +screen.service.error.header=\u672a\u8ba4\u8bc1\u6388\u6743\u7684\u670d\u52a1 +screen.service.error.message=\u4e0d\u5141\u8bb8\u4f7f\u7528CAS\u6765\u8ba4\u8bc1\u60a8\u8bbf\u95ee\u7684\u76ee\u6807\u5e94\u7528\u3002 +screen.service.empty.error.message=CAS\u7684\u670d\u52a1\u8bb0\u5f55\u662f\u7a7a\u7684\uff0c\u6ca1\u6709\u5b9a\u4e49\u670d\u52a1\u3002 \ +\u5e0c\u671b\u901a\u8fc7CAS\u8fdb\u884c\u8ba4\u8bc1\u7684\u5e94\u7528\u7a0b\u5e8f\u5fc5\u987b\u5728\u670d\u52a1\u8bb0\u5f55\u4e2d\u660e\u786e\u5b9a\u4e49\u3002 + +# Password policy +password.expiration.warning=\u4f60\u7684\u5bc6\u7801\u4f1a\u5728{0}\u5929\u5185\u8fc7\u671f\u3002\u8bf7\u7acb\u523b\u4fee\u6539\u4f60\u7684\u5bc6\u7801\u3002 +password.expiration.loginsRemaining=\u5728\u5fc5\u987b\u4fee\u6539\u5bc6\u7801\u4e4b\u524d\uff0c\u4f60\u8fd8\u5269{0}\u6b21\u767b\u5f55\u3002 +screen.accountdisabled.heading=\u8fd9\u4e2a\u8d26\u6237\u5df2\u7ecf\u88ab\u7981\u7528\u4e86\u3002 +screen.accountdisabled.message=\u8bf7\u8054\u7cfb\u7cfb\u7edf\u7ba1\u7406\u5458\u6765\u91cd\u65b0\u83b7\u5f97\u8bbf\u95ee\u6743\u9650\u3002 +screen.accountlocked.heading=\u8fd9\u4e2a\u8d26\u6237\u5df2\u7ecf\u88ab\u9501\u4f4f\u4e86\u3002 +screen.accountlocked.message=\u8bf7\u8054\u7cfb\u7cfb\u7edf\u7ba1\u7406\u5458\u6765\u91cd\u65b0\u83b7\u5f97\u8bbf\u95ee\u6743\u9650\u3002 +screen.expiredpass.heading=\u4f60\u7684\u5bc6\u7801\u5df2\u7ecf\u8fc7\u671f\u4e86\u3002 +screen.expiredpass.message=\u8bf7\u4fee\u6539\u4f60\u7684\u5bc6\u7801\u3002 +screen.mustchangepass.heading=\u4f60\u5fc5\u987b\u4fee\u6539\u4f60\u7684\u5bc6\u7801\u3002 +screen.mustchangepass.message=\u8bf7\u4fee\u6539\u4f60\u7684\u5bc6\u7801\u3002 +screen.badhours.heading=\u73b0\u5728\u4f60\u7684\u8d26\u6237\u88ab\u7981\u6b62\u767b\u5f55\u4e86\u3002 +screen.badhours.message=\u8bf7\u7a0d\u540e\u518d\u8bd5\u3002 +screen.badworkstation.heading=\u4f60\u4e0d\u80fd\u4ece\u8fd9\u4e2a\u5de5\u4f5c\u7ad9\u767b\u5f55\u3002 +screen.badworkstation.message=\u8bf7\u8054\u7cfb\u7cfb\u7edf\u7ba1\u7406\u5458\u6765\u91cd\u65b0\u83b7\u5f97\u8bbf\u95ee\u6743\u9650\u3002 + +# OAuth +screen.oauth.confirm.header=\u6388\u6743 +screen.oauth.confirm.message=\u8981\u6388\u6743"{0}"\u8bbf\u95ee\u4f60\u5168\u90e8\u4e2a\u4eba\u4fe1\u606f\u5417\uff1f +screen.oauth.confirm.allow=\u5141\u8bb8 + +# Unavailable +screen.unavailable.heading=CAS\u65e0\u6cd5\u4f7f\u7528 +screen.unavailable.message=\u5728\u8bd5\u56fe\u5b8c\u6210\u4f60\u7684\u8bf7\u6c42\u65f6\u51fa\u9519\u3002\u8bf7\u901a\u77e5\u4f60\u7684\u6280\u672f\u652f\u6301\u6216\u91cd\u8bd5\u3002 diff --git a/cas-server-webapp/src/main/resources/messages_zh_TW.properties b/cas-server-webapp/src/main/resources/messages_zh_TW.properties new file mode 100644 index 000000000000..01a403420e86 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_zh_TW.properties @@ -0,0 +1,68 @@ +#Welcome Screen Messages + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +screen.welcome.welcome=\u6b61\u8fce\u4f86\u5230\u4e2d\u592e\u8a8d\u8b49\u7cfb\u7d71\u3002\u9ed8\u8a8d\u7684\u8a8d\u8b49\u8655\u7406\u5668\u652f\u6301\u90a3\u4e9b\u7528\u6236\u540d\u7b49\u65bc\u5bc6\u78bc\u7684\u8cec\u865f\uff0c\u958b\u767c\u8005\u53ef\u4ee5\u8a66\u8a66\u770b\u3002 +screen.welcome.security=\u51fa\u65bc\u5b89\u5168\u8003\u616e\uff0c\u4e00\u65e6\u60a8\u8a2a\u554f\u904e\u90a3\u4e9b\u9700\u8981\u60a8\u63d0\u4f9b\u6191\u8b49\u4fe1\u606f\u7684\u61c9\u7528\u6642\uff0c\u8acb\u64cd\u4f5c\u5b8c\u6210\u4e4b\u5f8c\u95dc\u9589\u700f\u89bd\u5668\u3002 +screen.welcome.instructions=\u8acb\u8f38\u5165\u60a8\u7684\u7528\u6236\u540d\u548c\u5bc6\u78bc. +screen.welcome.label.netid=\u7528\u6236\u540d: +screen.welcome.label.netid.accesskey=n +screen.welcome.label.password=\u5bc6\u3000\u78bc: +screen.welcome.label.password.accesskey=p +screen.welcome.label.warn=\u8f49\u5411\u5176\u4ed6\u7ad9\u9ede\u524d\u63d0\u793a\u6211\u3002 +screen.welcome.label.warn.accesskey=w +screen.welcome.button.login=\u767b\u9304 +screen.welcome.button.clear=\u91cd\u7f6e + +# Blocked Errors Page +screen.blocked.header=\u8a2a\u554f\u88ab\u62d2\u7d55 +screen.blocked.message=\u8f38\u932f\u5bc6\u78bc\u6b21\u6578\u592a\u591a\uff0c\u8cec\u865f\u88ab\u9396\u5b9a\u3002 + +#Confirmation Screen Messages +screen.confirmation.message=\u55ae\u64ca\u9019\u88e1 \uff0c\u4fbf\u80fd\u5920\u8a2a\u554f\u5230\u76ee\u6a19\u61c9\u7528\u3002 + +#Generic Success Screen Messages +screen.success.header=\u767b\u9304\u6210\u529f +screen.success.success=\u60a8\u5df2\u7d93\u6210\u529f\u767b\u9304\u4e2d\u592e\u8a8d\u8b49\u7cfb\u7d71\u3002 +screen.success.security=\u51fa\u65bc\u5b89\u5168\u8003\u616e\uff0c\u4e00\u65e6\u60a8\u8a2a\u554f\u904e\u90a3\u4e9b\u9700\u8981\u60a8\u63d0\u4f9b\u6191\u8b49\u4fe1\u606f\u7684\u61c9\u7528\u6642\uff0c\u8acb\u64cd\u4f5c\u5b8c\u6210\u4e4b\u5f8c\u95dc\u9589\u700f\u89bd\u5668\u3002 + +#Logout Screen Messages +screen.logout.header=\u8a3b\u92b7\u6210\u529f +screen.logout.success=\u60a8\u5df2\u7d93\u6210\u529f\u9000\u51faCAS\u7cfb\u7d71\uff0c\u8b1d\u8b1d\u4f7f\u7528\uff01 +screen.logout.security=\u51fa\u65bc\u5b89\u5168\u8003\u616e\uff0c\u8acb\u95dc\u9589\u60a8\u7684\u700f\u89bd\u5668\u3002 +screen.logout.redirect=\u60a8\u53ef\u4ee5\u901a\u904e\u5982\u4e0bURL\u8a2a\u554f\u5230\u76ee\u6a19\u670d\u52d9\uff1a\u76ee\u6a19\u670d\u52d9. + +screen.service.sso.error.header=\u5728\u8a2a\u554f\u5230\u5230\u76ee\u6a19\u670d\u52d9\u524d\uff0c\u4f60\u5fc5\u9808\u7d93\u904e\u91cd\u65b0\u8a8d\u8b49\u7684\u8003\u9a57 +screen.service.sso.error.message=\u4f60\u6b63\u8a66\u5716\u8a2a\u554f\u8981\u6c42\u91cd\u65b0\u8a8d\u8b49\u7684\u670d\u52d9\u3002\u8acb\u5617\u8a66\u9032\u884c\u518d\u6b21\u8a8d\u8b49\u3002 + +error.invalid.loginticket=\u60a8\u4e0d\u80fd\u5920\u518d\u6b21\u63d0\u4ea4\u5df2\u7d93\u63d0\u4ea4\u904e\u7684\u8868\u55ae\u3002 +required.username=\u5fc5\u9808\u9304\u5165\u7528\u6236\u540d\u3002 +required.password=\u5fc5\u9808\u9304\u5165\u5bc6\u78bc\u3002 +error.authentication.credentials.bad=\u60a8\u63d0\u4f9b\u7684\u6191\u8b49\u6709\u8aa4\u3002 +error.authentication.credentials.unsupported=CAS\u4e0d\u652f\u6301\u60a8\u63d0\u4f9b\u7684\u6191\u8b49\u3002 + +INVALID_REQUEST_PROXY=\u5fc5\u9808\u540c\u6642\u63d0\u4f9b'pgt'\u548c'targetService'\u53c3\u6578 +INVALID_TICKET_SPEC=\u6821\u9a57\u7968\u6839\u5931\u6557\u3002\u60a8\u53ef\u80fd\u63a1\u7528\u670d\u52d9\u7968\u6839\u4f86\u6821\u9a57\u4ee3\u7406\u7968\u6839\uff0c\u6216\u6c92\u6709\u5c07renew\u8a2d\u70batrue\u3002 +INVALID_REQUEST=\u5fc5\u9808\u540c\u6642\u63d0\u4f9b'service'\u548c'ticket'\u53c3\u6578 +INVALID_TICKET=\u672a\u80fd\u5920\u8b58\u5225\u51fa\u76ee\u6a19''{0}''\u7968\u6839 +INVALID_SERVICE=\u7968\u6839''{0}''\u4e0d\u7b26\u5408\u76ee\u6a19\u670d\u52d9 + +screen.service.error.header=\u672a\u8a8d\u8b49\u6388\u6b0a\u7684\u670d\u52d9 +screen.service.error.message=\u4e0d\u5141\u8a31\u4f7f\u7528CAS\u4f86\u8a8d\u8b49\u60a8\u8a2a\u554f\u7684\u76ee\u6a19\u61c9\u7528\u3002 diff --git a/cas-server-webapp/src/main/resources/services/Apereo-10000002.json b/cas-server-webapp/src/main/resources/services/Apereo-10000002.json new file mode 100644 index 000000000000..4a500af25651 --- /dev/null +++ b/cas-server-webapp/src/main/resources/services/Apereo-10000002.json @@ -0,0 +1,30 @@ +{ + "@class" : "org.jasig.cas.services.RegexRegisteredService", + "serviceId" : "^https://www.apereo.org", + "name" : "Apereo", + "theme" : "apereo", + "id" : 10000002, + "description" : "Apereo foundation sample service", + "proxyPolicy" : { + "@class" : "org.jasig.cas.services.RefuseRegisteredServiceProxyPolicy" + }, + "evaluationOrder" : 1, + "usernameAttributeProvider" : { + "@class" : "org.jasig.cas.services.DefaultRegisteredServiceUsernameProvider" + }, + "logoutType" : "BACK_CHANNEL", + "attributeReleasePolicy" : { + "@class" : "org.jasig.cas.services.ReturnAllowedAttributeReleasePolicy", + "principalAttributesRepository" : { + "@class" : "org.jasig.cas.authentication.principal.DefaultPrincipalAttributesRepository" + }, + "authorizedToReleaseCredentialPassword" : false, + "authorizedToReleaseProxyGrantingTicket" : false + }, + "accessStrategy" : { + "@class" : "org.jasig.cas.services.DefaultRegisteredServiceAccessStrategy", + "enabled" : true, + "ssoEnabled" : true, + "requireAllAttributes" : true + } +} \ No newline at end of file diff --git a/cas-server-webapp/src/main/resources/services/HTTPSandIMAPS-10000001.json b/cas-server-webapp/src/main/resources/services/HTTPSandIMAPS-10000001.json new file mode 100644 index 000000000000..fa3751f9a050 --- /dev/null +++ b/cas-server-webapp/src/main/resources/services/HTTPSandIMAPS-10000001.json @@ -0,0 +1,29 @@ +{ + "@class" : "org.jasig.cas.services.RegexRegisteredService", + "serviceId" : "^(https|imaps)://.*", + "name" : "HTTPS and IMAPS", + "id" : 10000001, + "description" : "This service definition authorized all application urls that support HTTPS and IMAPS protocols.", + "proxyPolicy" : { + "@class" : "org.jasig.cas.services.RefuseRegisteredServiceProxyPolicy" + }, + "evaluationOrder" : 0, + "usernameAttributeProvider" : { + "@class" : "org.jasig.cas.services.DefaultRegisteredServiceUsernameProvider" + }, + "logoutType" : "BACK_CHANNEL", + "attributeReleasePolicy" : { + "@class" : "org.jasig.cas.services.ReturnAllowedAttributeReleasePolicy", + "principalAttributesRepository" : { + "@class" : "org.jasig.cas.authentication.principal.DefaultPrincipalAttributesRepository" + }, + "authorizedToReleaseCredentialPassword" : false, + "authorizedToReleaseProxyGrantingTicket" : false + }, + "accessStrategy" : { + "@class" : "org.jasig.cas.services.DefaultRegisteredServiceAccessStrategy", + "enabled" : true, + "ssoEnabled" : true, + "requireAllAttributes" : true + } +} \ No newline at end of file diff --git a/cas-server-webapp/src/main/resources/services/sample-access-strategy.json.sample b/cas-server-webapp/src/main/resources/services/sample-access-strategy.json.sample new file mode 100644 index 000000000000..2b5b842337be --- /dev/null +++ b/cas-server-webapp/src/main/resources/services/sample-access-strategy.json.sample @@ -0,0 +1,12 @@ +{ + "@class" : "org.jasig.cas.services.RegexRegisteredService", + "serviceId" : "sample", + "name" : "sample", + "id" : 1984, + "description" : "sample", + "accessStrategy" : { + "@class" : "org.jasig.cas.services.DefaultRegisteredServiceAccessStrategy", + "enabled" : true, + "ssoEnabled" : true + } +} diff --git a/cas-server-webapp/src/main/resources/services/sample-allowed-attributes.json.sample b/cas-server-webapp/src/main/resources/services/sample-allowed-attributes.json.sample new file mode 100644 index 000000000000..e0ff59f517fc --- /dev/null +++ b/cas-server-webapp/src/main/resources/services/sample-allowed-attributes.json.sample @@ -0,0 +1,11 @@ +{ + "@class" : "org.jasig.cas.services.RegexRegisteredService", + "serviceId" : "sample", + "name" : "sample", + "id" : 100, + "description" : "sample", + "attributeReleasePolicy" : { + "@class" : "org.jasig.cas.services.ReturnAllowedAttributeReleasePolicy", + "allowedAttributes" : [ "java.util.ArrayList", [ "cn", "mail", "sn" ] ] + } +} diff --git a/cas-server-webapp/src/main/resources/services/sample-authz-strategy.json.sample b/cas-server-webapp/src/main/resources/services/sample-authz-strategy.json.sample new file mode 100644 index 000000000000..a9847615712f --- /dev/null +++ b/cas-server-webapp/src/main/resources/services/sample-authz-strategy.json.sample @@ -0,0 +1,18 @@ +{ + "@class" : "org.jasig.cas.services.RegexRegisteredService", + "serviceId" : "sample", + "name" : "sample", + "id" : 666, + "description" : "sample", + "accessStrategy" : { + "@class" : "org.jasig.cas.services.DefaultRegisteredServiceAccessStrategy", + "enabled" : false, + "ssoEnabled" : false, + "requireAllAttributes" : true, + "requiredAttributes" : { + "@class" : "java.util.HashMap", + "cn" : [ "java.util.HashSet", [ "v1, v2, v3" ] ], + "memberOf" : [ "java.util.HashSet", [ "v4, v5, v6" ] ] + } + } +} diff --git a/cas-server-webapp/src/main/resources/services/sample-filtered-attributes.json.sample b/cas-server-webapp/src/main/resources/services/sample-filtered-attributes.json.sample new file mode 100644 index 000000000000..7c8844b49ab3 --- /dev/null +++ b/cas-server-webapp/src/main/resources/services/sample-filtered-attributes.json.sample @@ -0,0 +1,15 @@ +{ + "@class" : "org.jasig.cas.services.RegexRegisteredService", + "serviceId" : "sample", + "name" : "sample", + "id" : 200, + "description" : "sample", + "attributeReleasePolicy" : { + "@class" : "org.jasig.cas.services.ReturnAllowedAttributeReleasePolicy", + "attributeFilter" : { + "@class" : "org.jasig.cas.services.support.RegisteredServiceRegexAttributeFilter", + "pattern" : "\\w+" + }, + "allowedAttributes" : [ "java.util.ArrayList", [ "cn", "mail", "givenName" ] ] + } +} diff --git a/cas-server-webapp/src/main/resources/services/sample-mapped-attributes.json.sample b/cas-server-webapp/src/main/resources/services/sample-mapped-attributes.json.sample new file mode 100644 index 000000000000..b61a625d915f --- /dev/null +++ b/cas-server-webapp/src/main/resources/services/sample-mapped-attributes.json.sample @@ -0,0 +1,15 @@ +{ + "@class" : "org.jasig.cas.services.RegexRegisteredService", + "serviceId" : "sample", + "name" : "sample", + "id" : 300, + "description" : "sample", + "attributeReleasePolicy" : { + "@class" : "org.jasig.cas.services.ReturnMappedAttributeReleasePolicy", + "allowedAttributes" : { + "@class" : "java.util.TreeMap", + "attr1" : "newattr1", + "attr2" : "newattr2" + } + } +} diff --git a/cas-server-webapp/src/main/resources/services/sample-proxy-authz.json.sample b/cas-server-webapp/src/main/resources/services/sample-proxy-authz.json.sample new file mode 100644 index 000000000000..74e45268a3e7 --- /dev/null +++ b/cas-server-webapp/src/main/resources/services/sample-proxy-authz.json.sample @@ -0,0 +1,11 @@ +{ + "@class" : "org.jasig.cas.services.RegexRegisteredService", + "serviceId" : "sample", + "name" : "sample", + "id" : 400, + "description" : "sample", + "proxyPolicy" : { + "@class" : "org.jasig.cas.services.RegexMatchingRegisteredServiceProxyPolicy", + "pattern" : "^https://.+" + } +} diff --git a/cas-server-webapp/src/main/resources/services/sample-public-key.json.sample b/cas-server-webapp/src/main/resources/services/sample-public-key.json.sample new file mode 100644 index 000000000000..d6aec6d96bfd --- /dev/null +++ b/cas-server-webapp/src/main/resources/services/sample-public-key.json.sample @@ -0,0 +1,19 @@ +{ + "@class" : "org.jasig.cas.services.RegexRegisteredService", + "id" : 10000001, + "description" : "This service definition authorized all application urls that support HTTPS and IMAPS protocols.", + "serviceId" : "^(https|imaps)://.*", + "name": "HTTPS and IMAPS", + "evaluationOrder" : 10000001, + "attributeReleasePolicy" : { + "@class" : "org.jasig.cas.services.ReturnAllowedAttributeReleasePolicy", + "authorizedToReleaseCredentialPassword": false, + "authorizedToReleaseProxyGrantingTicket": false, + "allowedAttributes" : [ "java.util.ArrayList", [ "cn", "mail" ] ] + }, + "publicKey" : { + "@class" : "org.jasig.cas.services.RegisteredServicePublicKeyImpl", + "location" : "classpath:RSA1024Public.key", + "algorithm" : "RSA" + } +} diff --git a/cas-server-webapp/src/main/resources/services/sample-username-anon.json.sample b/cas-server-webapp/src/main/resources/services/sample-username-anon.json.sample new file mode 100644 index 000000000000..961183a3e9de --- /dev/null +++ b/cas-server-webapp/src/main/resources/services/sample-username-anon.json.sample @@ -0,0 +1,14 @@ +{ + "@class" : "org.jasig.cas.services.RegexRegisteredService", + "serviceId" : "sample", + "name" : "sample", + "id" : 500, + "description" : "sample", + "usernameAttributeProvider" : { + "@class" : "org.jasig.cas.services.AnonymousRegisteredServiceUsernameAttributeProvider", + "persistentIdGenerator" : { + "@class" : "org.jasig.cas.authentication.principal.ShibbolethCompatiblePersistentIdGenerator", + "salt" : "aGVsbG93b3JsZA==" + } + } +} diff --git a/cas-server-webapp/src/main/resources/services/sample-username-service.json.sample b/cas-server-webapp/src/main/resources/services/sample-username-service.json.sample new file mode 100644 index 000000000000..7153858d5f9e --- /dev/null +++ b/cas-server-webapp/src/main/resources/services/sample-username-service.json.sample @@ -0,0 +1,11 @@ +{ + "@class" : "org.jasig.cas.services.RegexRegisteredService", + "serviceId" : "sample", + "name" : "sample", + "id" : 600, + "description" : "sample", + "usernameAttributeProvider" : { + "@class" : "org.jasig.cas.services.PrincipalAttributeRegisteredServiceUsernameProvider", + "usernameAttribute" : "cn" + } +} diff --git a/cas-server-webapp/src/main/resources/truststore.jks b/cas-server-webapp/src/main/resources/truststore.jks new file mode 100644 index 000000000000..c408465500cb Binary files /dev/null and b/cas-server-webapp/src/main/resources/truststore.jks differ diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/cas-servlet.xml b/cas-server-webapp/src/main/webapp/WEB-INF/cas-servlet.xml new file mode 100644 index 000000000000..8a0f266a3a9f --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/cas-servlet.xml @@ -0,0 +1,353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cas_views + + + + + + + + + + + + + + + + + + + + + + + serviceValidateController + proxyValidateController + + + + v3ServiceValidateController + v3ProxyValidateController + legacyValidateController + proxyController + passThroughController + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/cas.properties b/cas-server-webapp/src/main/webapp/WEB-INF/cas.properties new file mode 100644 index 000000000000..40c854e02aa6 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/cas.properties @@ -0,0 +1,215 @@ +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +server.name=http://localhost:8080 +server.prefix=${server.name}/cas + +# Spring Security's EL-based access rules for the /status URI of CAS that exposes health check information +cas.securityContext.status.access=hasIpAddress('127.0.0.1') + +# Spring Security's EL-based access rules for the /statistics URI of CAS that exposes stats about the CAS server +cas.securityContext.statistics.access=hasIpAddress('127.0.0.1') + +cas.themeResolver.defaultThemeName=cas-theme-default + +# Path prefix for where views are to be found +# cas.viewResolver.defaultViewsPathPrefix=/WEB-INF/view/jsp/default/ui/ + +# Location of the Spring xml config file where views may be collected +# cas.viewResolver.xmlFile=/META-INF/spring/views.xml + +## +# Unique CAS node name +# host.name is used to generate unique Service Ticket IDs and SAMLArtifacts. This is usually set to the specific +# hostname of the machine running the CAS node, but it could be any label so long as it is unique in the cluster. +host.name=cas01.example.org + +## +# Database flavors for Hibernate +# +# One of these is needed if you are storing Services or Tickets in an RDBMS via JPA. +# +# database.hibernate.dialect=org.hibernate.dialect.OracleDialect +# database.hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect +# database.hibernate.dialect=org.hibernate.dialect.HSQLDialect +# database.hibernate.batchSize=10 + +## +# CAS SSO Cookie Generation & Security +# See https://github.com/mitreid-connect/json-web-key-generator +# +# Do note that the following settings MUST be generated per deployment. +# +# Defaults at spring-configuration/ticketGrantingTicketCookieGenerator.xml +# The encryption secret key. By default, must be a octet string of size 256. +tgc.encryption.key=1PbwSbnHeinpkZOSZjuSJ8yYpUrInm5aaV18J2Ar4rM + +# The signing secret key. By default, must be a octet string of size 512. +tgc.signing.key=szxK-5_eJjs-aUj-64MpUZ-GPPzGLhYPLGl0wrYjYNVAGva2P0lLe6UGKGM7k8dWxsOVGutZWgvmY3l5oVPO3w + +## +# CAS Logout Behavior +# WEB-INF/cas-servlet.xml +# +# Specify whether CAS should redirect to the specified service parameter on /logout requests +# cas.logout.followServiceRedirects=false + +## +# CAS Cached Attributes Timeouts +# Controls the cached attribute expiration policy +# +# Notes the duration in which attributes will be kept alive +# cas.attrs.timeToExpireInHours=2 + +## +# Single Sign-On Session +# +# Indicates whether an SSO session should be created for renewed authentication requests. +# create.sso.renewed.authn=true +# +# Indicates whether an SSO session can be created if no service is present. +# create.sso.missing.service=true + +## +# Spring Webflow Web Application Session +# Define the settings that are required to encrypt and persist the CAS web application session. +# See the cas-servlet.xml file to understand how these properties are used. +# +# cas.webflow.cipher.alg=AES +# cas.webflow.cipher.mode=CBC +# cas.webflow.cipher.padding=PKCS7 +# cas.webflow.keystore=classpath:/etc/keystore.jceks +# cas.webflow.keystore.type=JCEKS +# cas.webflow.keystore.password=changeit +# cas.webflow.keyalias=aes128 +# cas.webflow.keypassword=changeit +## +# Single Sign-On Session Timeouts +# Defaults sourced from WEB-INF/spring-configuration/ticketExpirationPolices.xml +# +# Maximum session timeout - TGT will expire in maxTimeToLiveInSeconds regardless of usage +# tgt.maxTimeToLiveInSeconds=28800 +# +# Idle session timeout - TGT will expire sooner than maxTimeToLiveInSeconds if no further requests +# for STs occur within timeToKillInSeconds +# tgt.timeToKillInSeconds=7200 + +## +# Service Ticket Timeout +# Default sourced from WEB-INF/spring-configuration/ticketExpirationPolices.xml +# +# Service Ticket timeout - typically kept short as a control against replay attacks, default is 10s. You'll want to +# increase this timeout if you are manually testing service ticket creation/validation via tamperdata or similar tools +# st.timeToKillInSeconds=10 + +## +# Http Client Settings +# +# The http client read timeout in milliseconds +# http.client.read.timeout=5000 + +# The http client connection timeout in milliseconds +# http.client.connection.timeout=5000 +# +# The http client truststore file, in addition to the default's +# http.client.truststore.file=classpath:truststore.jks +# +# The http client truststore's password +# http.client.truststore.psw=changeit + +## +# Single Logout Out Callbacks +# Default sourced from WEB-INF/spring-configuration/argumentExtractorsConfiguration.xml +# +# To turn off all back channel SLO requests set this to true +# slo.callbacks.disabled=false +# +# To send callbacks to endpoints synchronously, set this to false +# slo.callbacks.asynchronous=true + +## +# CAS Protocol Security Filter +# +# Are multi-valued parameters accepted? +# cas.http.allow.multivalue.params=false + +# Define the list of request parameters to examine for sanity +# cas.http.check.params=ticket,service,renew,gateway,warn,target,SAMLart,pgtUrl,pgt,pgtId,pgtIou,targetService + +# Define the list of request parameters only allowed via POST +# cas.http.allow.post.params=username,password + +## +# JSON Service Registry +# +# Directory location where JSON service files may be found. +# service.registry.config.location=classpath:services + +## +# Service Registry Periodic Reloading Scheduler +# Default sourced from WEB-INF/spring-configuration/applicationContext.xml +# +# Force a startup delay of 2 minutes. +# service.registry.quartz.reloader.startDelay=120000 +# +# Reload services every 2 minutes +# service.registry.quartz.reloader.repeatInterval=120000 + +## +# Log4j +# Default sourced from WEB-INF/spring-configuration/log4jConfiguration.xml: +# +# It is often time helpful to externalize log4j.xml to a system path to preserve settings between upgrades. +# log4j.config.location=file:///etc/cas/log4j2.xml +# log4j.config.location=classpath:log4j2.xml + +## +# Metrics +# Default sourced from WEB-INF/spring-configuration/metricsConfiguration.xml: +# +# Define how often should metric data be reported. Default is 30 seconds. +# metrics.refresh.internal=30s + +## +# Encoding +# +# Set the encoding to use for requests. Default is UTF-8 +# httprequest.web.encoding=UTF-8 + +# Default is true. Switch this to "false" to not enforce the specified encoding in any case, +# applying it as default response encoding as well. +# httprequest.web.encoding.force=true + +## +# Reports +# +# Setting to whether include the ticket granting ticket id in the report +# sso.sessions.include.tgt=false + +## +# Password Policy +# +# Warn all users of expiration date regardless of warningDays value. +# password.policy.warnAll=false + +# Threshold number of days to begin displaying password expiration warnings. +# password.policy.warningDays=30 + +# URL to which the user will be redirected to change the password. +# password.policy.url=https://password.example.edu/change diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/deployerConfigContext.xml b/cas-server-webapp/src/main/webapp/WEB-INF/deployerConfigContext.xml new file mode 100644 index 000000000000..1a05ac941380 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/deployerConfigContext.xml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + memberOf + + faculty + staff + org + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/restlet-servlet.xml b/cas-server-webapp/src/main/webapp/WEB-INF/restlet-servlet.xml new file mode 100644 index 000000000000..efeaefcaa0c0 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/restlet-servlet.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/README.txt b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/README.txt new file mode 100644 index 000000000000..72912919aca8 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/README.txt @@ -0,0 +1,26 @@ +INTRODUCTION +The spring-configuration directory is a "convention-over-configuration" option +for CAS deployers. It allows you to drop a Spring XML configuration file into +this directory and have CAS automatically find it (after the typical application +restart). It eliminates the need for you to register that file in the web.xml + +ADVANTAGES +By automatically breaking the configuration into smaller "bite-sized" pieces +you can easily override small components of CAS without worrying about merging +huge pieces of configurations files together later. + +The configuration-over-convention option also allows you to add new configuration +options without editing existing configuration files. + +This should make tracking changes and maintaining local modifications easier. + +GOTCHAS AND THINGS TO WATCH OUT FOR +If you name a local bean and an existing bean the same thing, there will be a major +collision. Deployment will fail. The sky will fall! (okay that last part isn't +true). Spring will be merging all of these files together so every bean must +have unique names. The only way around this is if you override the file completely. +i.e. override the ticketRegistry.xml allows you to re-use the "ticketRegistry" +id. + +In addition, if there is a typographical/XML parsing error in a file, the +application will not deploy. diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/applicationContext.xml b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/applicationContext.xml new file mode 100644 index 000000000000..16f36549db93 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/applicationContext.xml @@ -0,0 +1,141 @@ + + + + + This is the main Spring configuration file with some of the main "core" classes defined. You shouldn't really + modify this unless you know what you're doing! + + + + + + + + + + classpath:custom_messages + classpath:messages + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/argumentExtractorsConfiguration.xml b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/argumentExtractorsConfiguration.xml new file mode 100644 index 000000000000..f37891ae97b8 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/argumentExtractorsConfiguration.xml @@ -0,0 +1,45 @@ + + + + + Argument Extractors are what are used to translate HTTP requests into requests of the appropriate protocol (i.e. + CAS, SAML, SAML2, + OpenId, etc.). By default, only CAS is enabled. + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/auditTrailContext.xml b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/auditTrailContext.xml new file mode 100644 index 000000000000..b1974c298498 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/auditTrailContext.xml @@ -0,0 +1,108 @@ + + + + + + Configuration file for the Inspektr package which handles auditing for Java applications. + If enabled this should be modified to log audit and statistics information the same way + your local applications do. The default is currently to log to the console which is good + for debugging/testing purposes. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/filters.xml b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/filters.xml new file mode 100644 index 000000000000..454b5d26324a --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/filters.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/log4jConfiguration.xml b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/log4jConfiguration.xml new file mode 100644 index 000000000000..94c6d1359d33 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/log4jConfiguration.xml @@ -0,0 +1,43 @@ + + + + + + Log4J initialization. Configuration options are sourced from cas.properties. This allows deployers to externalize + both cas.properties and log4j.xml, so that a single cas.war file can be deployed to multiple tiers or hosts without + having to do any post configuration. This approach helps to preserve configuration between upgrades. + + Deployers should not have to edit this file. + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/metricsContext.xml b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/metricsContext.xml new file mode 100644 index 000000000000..4f1757e14cc0 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/metricsContext.xml @@ -0,0 +1,58 @@ + + + + + This is the Spring configuration file that orchestrates the CAS metrics. + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/propertyFileConfigurer.xml b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/propertyFileConfigurer.xml new file mode 100644 index 000000000000..252437c67985 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/propertyFileConfigurer.xml @@ -0,0 +1,43 @@ + + + + + This file lets CAS know where you've stored the cas.properties file which details some of the configuration + options + that are specific to your environment. You can specify the location of the file here. You may wish to place the + file outside + of the Servlet context if you have options that are specific to a tier (i.e. test vs. production) so that the + WAR file + can be moved between tiers without modification. + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/protocolViewsConfiguration.xml b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/protocolViewsConfiguration.xml new file mode 100644 index 000000000000..cbf7b2f68e03 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/protocolViewsConfiguration.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/securityContext.xml b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/securityContext.xml new file mode 100644 index 000000000000..ec98e01f28ae --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/securityContext.xml @@ -0,0 +1,44 @@ + + + + + + Security configuration for sensitive areas of CAS : status and statistics. + + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/ticketExpirationPolicies.xml b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/ticketExpirationPolicies.xml new file mode 100644 index 000000000000..d46403c91555 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/ticketExpirationPolicies.xml @@ -0,0 +1,47 @@ + + + + + Assignment of expiration policies for the different tickets generated by CAS including ticket granting ticket + (TGT), service ticket (ST), proxy granting ticket (PGT), and proxy ticket (PT). + These expiration policies determine how long the ticket they are assigned to can be used and even how often they + can be used before becoming expired / invalid. + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml new file mode 100644 index 000000000000..5ac20c9c45c4 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml @@ -0,0 +1,57 @@ + + + + + Defines the cookie that stores the TicketGrantingTicket. You most likely should never modify these (especially + the "secure" property). + You can change the name if you want to make it harder for people to guess. + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/ticketRegistry.xml b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/ticketRegistry.xml new file mode 100644 index 000000000000..2d8ab9dbbf15 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/ticketRegistry.xml @@ -0,0 +1,51 @@ + + + + + Configuration for the default TicketRegistry which stores the tickets in-memory and cleans them out as specified + intervals. + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/uniqueIdGenerators.xml b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/uniqueIdGenerators.xml new file mode 100644 index 000000000000..d3548e230dc4 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/uniqueIdGenerators.xml @@ -0,0 +1,64 @@ + + + + + Controls the generation of the unique identifiers for tickets. You most likely do not need to modify these. Though you may need to add + the SAML ticket id generator. + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/warnCookieGenerator.xml b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/warnCookieGenerator.xml new file mode 100644 index 000000000000..015ed82c6ee3 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/warnCookieGenerator.xml @@ -0,0 +1,38 @@ + + + + + This Spring Configuration file describes the cookie used to store the WARN parameter so that a user is warned + whenever the CAS service + is used. You would modify this if you wanted to change the cookie path or the name. + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/unused-spring-configuration/clearpass-configuration.xml b/cas-server-webapp/src/main/webapp/WEB-INF/unused-spring-configuration/clearpass-configuration.xml new file mode 100644 index 000000000000..ab9b305ae689 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/unused-spring-configuration/clearpass-configuration.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + clearPassController + + + + + + + + + + + + + + + ... + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/unused-spring-configuration/lppe-configuration.xml b/cas-server-webapp/src/main/webapp/WEB-INF/unused-spring-configuration/lppe-configuration.xml new file mode 100644 index 000000000000..c885f551d71d --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/unused-spring-configuration/lppe-configuration.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/unused-spring-configuration/mbeans.xml b/cas-server-webapp/src/main/webapp/WEB-INF/unused-spring-configuration/mbeans.xml new file mode 100644 index 000000000000..6bee8b103d1d --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/unused-spring-configuration/mbeans.xml @@ -0,0 +1,52 @@ + + + + + Configuration for the MBeans to support JMX. + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/authorizationFailure.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/authorizationFailure.jsp new file mode 100644 index 000000000000..fe4acdd9d851 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/authorizationFailure.jsp @@ -0,0 +1,43 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + + +<%@ page isErrorPage="true" %> +<%@ page import="org.jasig.cas.web.support.WebUtils"%> + +
+

${pageContext.errorData.statusCode} -

+ + <% + Object casAcessDeniedKey = request.getAttribute(WebUtils.CAS_ACCESS_DENIED_REASON); + request.setAttribute("casAcessDeniedKey", casAcessDeniedKey); + + %> + + + +

+
+
+

<%=request.getAttribute("javax.servlet.error.message")%>

+

+
+ diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casAcceptableUsagePolicyView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casAcceptableUsagePolicyView.jsp new file mode 100644 index 000000000000..19e36ff0e8da --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casAcceptableUsagePolicyView.jsp @@ -0,0 +1,46 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + +
+ + +

Acceptable Usage Policy

+
+ The purpose of this policy is to establish acceptable and unacceptable use of electronic devices and network resources in conjunction with the established culture of ethical and lawful behavior, openness, trust, and integrity. + +

+ By using these resources, you agree to abide by the Acceptable Usage Policy. +

+ +

Click '' to continue. Otherwise, click ''.

+
+ +
+ + + " type="submit" /> + " type="button" + onclick="location.href = location.href;" /> +
+
+
+ diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casAccountDisabledView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casAccountDisabledView.jsp new file mode 100644 index 000000000000..a0075b08b0e8 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casAccountDisabledView.jsp @@ -0,0 +1,26 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + +
+

+

+
+ diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casAccountLockedView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casAccountLockedView.jsp new file mode 100644 index 000000000000..41b97c4e5aee --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casAccountLockedView.jsp @@ -0,0 +1,26 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + +
+

+

+
+ diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casBadHoursView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casBadHoursView.jsp new file mode 100644 index 000000000000..6ec28f5944f9 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casBadHoursView.jsp @@ -0,0 +1,26 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + +
+

+

+
+ diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casBadWorkstationView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casBadWorkstationView.jsp new file mode 100644 index 000000000000..dbea9e870f32 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casBadWorkstationView.jsp @@ -0,0 +1,26 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + +
+

+

+
+ diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casConfirmView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casConfirmView.jsp new file mode 100644 index 000000000000..5d0bf6996b50 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casConfirmView.jsp @@ -0,0 +1,25 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + +
+

+
+ \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casExpiredPassView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casExpiredPassView.jsp new file mode 100644 index 000000000000..41a521e5f1a9 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casExpiredPassView.jsp @@ -0,0 +1,26 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + +
+

+

+
+ diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casGenericSuccessView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casGenericSuccessView.jsp new file mode 100644 index 000000000000..53e3f836b4c3 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casGenericSuccessView.jsp @@ -0,0 +1,28 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + +
+

+

+

+
+ + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginMessageView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginMessageView.jsp new file mode 100644 index 000000000000..8a6d7e5129a7 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginMessageView.jsp @@ -0,0 +1,37 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + + +
+

Authentication Succeeded with Warnings

+ + +

${message.text}

+
+ +
+ + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.jsp new file mode 100644 index 000000000000..f61374074831 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.jsp @@ -0,0 +1,203 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + + + +
+

+

+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

${fn:escapeXml(registeredServiceName)}

+

${fn:escapeXml(registeredServiceDescription)}

+
+
+

+ + +

+ + + + +

+ +
+ + + + + " /> + + + + + + +
+ +
+ + <%-- + NOTE: Certain browsers will offer the option of caching passwords for a user. There is a non-standard attribute, + "autocomplete" that when set to "off" will tell certain browsers not to prompt to cache credentials. For more + information, see the following web page: + http://www.technofundo.com/tech/web/ie_autocomplete.html + --%> + + + +
+ + + +
+ + + + + " tabindex="6" type="submit" /> + " tabindex="7" type="reset" /> +
+
+
+ + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLogoutView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLogoutView.jsp new file mode 100644 index 000000000000..ace6364e8776 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLogoutView.jsp @@ -0,0 +1,27 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + +
+

+

+

+
+ \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casMustChangePassView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casMustChangePassView.jsp new file mode 100644 index 000000000000..ae7249b2d957 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casMustChangePassView.jsp @@ -0,0 +1,26 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + +
+

+

+
+ diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/includes/bottom.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/includes/bottom.jsp new file mode 100644 index 000000000000..b79a0208f014 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/includes/bottom.jsp @@ -0,0 +1,38 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/includes/top.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/includes/top.jsp new file mode 100644 index 000000000000..2e5ed3f60003 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/includes/top.jsp @@ -0,0 +1,50 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + + +<%@ page pageEncoding="UTF-8" %> +<%@ page contentType="text/html; charset=UTF-8" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> + + + + + + CAS – Central Authentication Service + + + " /> + " type="image/x-icon" /> + + + + +
+
+ +

Central Authentication Service (CAS)

+
+
diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/serviceErrorSsoView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/serviceErrorSsoView.jsp new file mode 100644 index 000000000000..da7cc4a6f50c --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/serviceErrorSsoView.jsp @@ -0,0 +1,31 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + + + + + + +
+

+

+
+ diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/serviceErrorView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/serviceErrorView.jsp new file mode 100644 index 000000000000..c5629295fb33 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/serviceErrorView.jsp @@ -0,0 +1,26 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + +
+

+

+
+ diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/errors.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/errors.jsp new file mode 100644 index 000000000000..c54db9adc0c8 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/errors.jsp @@ -0,0 +1,26 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + +
+

+

+
+ diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/monitoring/viewConfig.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/monitoring/viewConfig.jsp new file mode 100644 index 000000000000..415f3d24c104 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/monitoring/viewConfig.jsp @@ -0,0 +1,145 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@include file="/WEB-INF/view/jsp/default/ui/includes/top.jsp"%> + + + + + + + + + +
+
+

CAS Server Internal Configuration

+

+

Please wait...
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+

+ +
+ +
+
+ + +<%@include file="/WEB-INF/view/jsp/default/ui/includes/bottom.jsp" %> diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/monitoring/viewSsoSessions.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/monitoring/viewSsoSessions.jsp new file mode 100644 index 000000000000..670512e2fddf --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/monitoring/viewSsoSessions.jsp @@ -0,0 +1,99 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@include file="/WEB-INF/view/jsp/default/ui/includes/top.jsp"%> + + + + + + +
+
+

SSO Sessions Report

+

+

+ +
+ +
+

+ +
+ +
+
+ + +<%@include file="/WEB-INF/view/jsp/default/ui/includes/bottom.jsp" %> diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/monitoring/viewStatistics.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/monitoring/viewStatistics.jsp new file mode 100644 index 000000000000..d3d19a02a57e --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/monitoring/viewStatistics.jsp @@ -0,0 +1,128 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@include file="/WEB-INF/view/jsp/default/ui/includes/top.jsp"%> + + + +

Runtime Statistics

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
Server${serverIpAddress} (${serverHostName})
CAS Ticket Suffix${casTicketSuffix}
Server Start Time${startTime}
Uptime${upTime}
Memory ${freeMemory} MB free ${totalMemory} MB total
Maximum Memory${maxMemory} MB
Available Processors${availableProcessors}
+ +

+ +

Ticket Registry Statistics

+ + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
Unexpired TGTs${unexpiredTgts}
Unexpired STs${unexpiredSts}
Expired TGTs${expiredTgts}
Expired STs${expiredSts}
+ +

Performance Statistics

+Metrics +Ping +Thread Dump + +

Reports

+SSO Sessions + +<%@include file="/WEB-INF/view/jsp/default/ui/includes/bottom.jsp" %> diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casProxyFailureView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casProxyFailureView.jsp new file mode 100644 index 000000000000..088e610fc468 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casProxyFailureView.jsp @@ -0,0 +1,27 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@ page session="false" contentType="application/xml; charset=UTF-8" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> + + + ${fn:escapeXml(description)} + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casProxySuccessView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casProxySuccessView.jsp new file mode 100644 index 000000000000..77c8d187b53f --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casProxySuccessView.jsp @@ -0,0 +1,27 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@ page session="false" contentType="application/xml; charset=UTF-8" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> + + + ${fn:escapeXml(ticket)} + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationFailure.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationFailure.jsp new file mode 100644 index 000000000000..5d75a7962283 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationFailure.jsp @@ -0,0 +1,27 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@ page session="false" contentType="application/xml; charset=UTF-8" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> + + + ${fn:escapeXml(description)} + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp new file mode 100644 index 000000000000..90b4451f3e63 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp @@ -0,0 +1,38 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@ page session="false" contentType="application/xml; charset=UTF-8" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> + + + ${fn:escapeXml(principal.id)} + + ${pgtIou} + + + + + ${fn:escapeXml(proxy.principal.id)} + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/3.0/casServiceValidationFailure.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/3.0/casServiceValidationFailure.jsp new file mode 100644 index 000000000000..5d75a7962283 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/3.0/casServiceValidationFailure.jsp @@ -0,0 +1,27 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@ page session="false" contentType="application/xml; charset=UTF-8" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> + + + ${fn:escapeXml(description)} + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/3.0/casServiceValidationSuccess.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/3.0/casServiceValidationSuccess.jsp new file mode 100644 index 000000000000..ac28803f2e66 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/3.0/casServiceValidationSuccess.jsp @@ -0,0 +1,56 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@ page session="false" contentType="application/xml; charset=UTF-8" %> +<%@ page import="java.util.*, java.util.Map.Entry" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> + + + ${fn:escapeXml(principal.id)} + + ${pgtIou} + + + + + ${fn:escapeXml(proxy.principal.id)} + + + + + + + + + + ${fn:escapeXml(attrval)} + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/casPostResponseView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/casPostResponseView.jsp new file mode 100644 index 000000000000..b2fad1a7c32d --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/casPostResponseView.jsp @@ -0,0 +1,37 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@ page language="java" session="false"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + +
" method="post"> +
+ + + +
+ +
+ + \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/clearPass/clearPassFailure.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/clearPass/clearPassFailure.jsp new file mode 100644 index 000000000000..f28de9b78ca3 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/clearPass/clearPassFailure.jsp @@ -0,0 +1,25 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@ page session="false" contentType="application/xml; charset=UTF-8" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> + + ${fn:escapeXml(description)} + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/clearPass/clearPassSuccess.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/clearPass/clearPassSuccess.jsp new file mode 100644 index 000000000000..d0eb67e528ad --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/clearPass/clearPassSuccess.jsp @@ -0,0 +1,28 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@ page session="false" contentType="application/xml; charset=UTF-8" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> + + + ${fn:escapeXml(credentials)} + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/oauth/confirm.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/oauth/confirm.jsp new file mode 100644 index 000000000000..2eea70426ed4 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/oauth/confirm.jsp @@ -0,0 +1,33 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + +
+

+ +

+ +

+ +

+ +

+
+ diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdAssociationFailureView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdAssociationFailureView.jsp new file mode 100644 index 000000000000..2c168663306e --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdAssociationFailureView.jsp @@ -0,0 +1,21 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%= "openid.mode:cancel\n" %> \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdAssociationSuccessView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdAssociationSuccessView.jsp new file mode 100644 index 000000000000..3aa5bed31a38 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdAssociationSuccessView.jsp @@ -0,0 +1,30 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@ page import="java.util.Set, java.util.Map, java.util.Iterator" %> +<% + Map parameters = (Map)request.getAttribute("parameters"); + Iterator iterator = parameters.keySet().iterator(); + while (iterator.hasNext()) { + String key = (String)iterator.next(); + String parameter = (String)parameters.get(key); + out.print(key+":"+parameter+"\n"); + } +%> \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdServiceFailureView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdServiceFailureView.jsp new file mode 100644 index 000000000000..90f0555cb4c0 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdServiceFailureView.jsp @@ -0,0 +1,21 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%= "openid.mode:id_res\nis_valid:false\n" %> \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdServiceSuccessView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdServiceSuccessView.jsp new file mode 100644 index 000000000000..9ac4893a60e6 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdServiceSuccessView.jsp @@ -0,0 +1,21 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%= "openid.mode:id_res\nis_valid:true\n" %> \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/user.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/user.jsp new file mode 100644 index 000000000000..496fa682b6cb --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/user.jsp @@ -0,0 +1,25 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/web.xml b/cas-server-webapp/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000000..7c2739c28edc --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,282 @@ + + + + Central Authentication System (CAS) 4.1.0-SNAPSHOT + + + isLog4jAutoInitializationDisabled + true + + + + contextConfigLocation + + /WEB-INF/spring-configuration/*.xml + /WEB-INF/deployerConfigContext.xml + + classpath*:/META-INF/spring/*.xml + + + + + characterEncodingFilter + org.springframework.web.filter.DelegatingFilterProxy + + + characterEncodingFilter + /* + + + + CAS Client Info Logging Filter + org.jasig.inspektr.common.web.ClientInfoThreadLocalFilter + + + CAS Client Info Logging Filter + /* + + + + requestParameterSecurityFilter + org.springframework.web.filter.DelegatingFilterProxy + + + requestParameterSecurityFilter + /* + + + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + springSecurityFilterChain + /status + + + springSecurityFilterChain + /statistics/* + + + + + + org.springframework.web.context.ContextLoaderListener + + + + + org.jasig.cas.CasEnvironmentContextListener + + + + + + cas + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/cas-servlet.xml, /WEB-INF/cas-servlet-*.xml + + + publishContext + false + + 1 + + + + metrics-health + com.codahale.metrics.servlets.HealthCheckServlet + + + + metrics + com.codahale.metrics.servlets.MetricsServlet + + + + metrics-ping + com.codahale.metrics.servlets.PingServlet + + + + metrics-threads + com.codahale.metrics.servlets.ThreadDumpServlet + + + + cas + /login + + + + cas + /logout + + + + cas + /validate + + + + cas + /serviceValidate + + + + cas + /p3/serviceValidate + + + + cas + /proxy + + + + cas + /proxyValidate + + + + cas + /p3/proxyValidate + + + + cas + /CentralAuthenticationService + + + + cas + /status + + + + cas + /statistics + + + + cas + /statistics/ssosessions + + + + cas + /status/config + + + + metrics-ping + /statistics/ping + + + + metrics + /statistics/metrics + + + + metrics-threads + /statistics/threads + + + + metrics-health + /statistics/healthcheck + + + + cas + /authorizationFailure.html + + + + cas + /v1/* + + + + + + + 5 + + + + 401 + /authorizationFailure.html + + + + 403 + /authorizationFailure.html + + + + 404 + / + + + + 500 + /WEB-INF/view/jsp/errors.jsp + + + + 501 + /WEB-INF/view/jsp/errors.jsp + + + + 503 + /WEB-INF/view/jsp/errors.jsp + + + + index.jsp + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/webflow/login/login-webflow.xml b/cas-server-webapp/src/main/webapp/WEB-INF/webflow/login/login-webflow.xml new file mode 100644 index 000000000000..78e33336dc4f --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/webflow/login/login-webflow.xml @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/webflow/logout/logout-webflow.xml b/cas-server-webapp/src/main/webapp/WEB-INF/webflow/logout/logout-webflow.xml new file mode 100644 index 000000000000..fd6e4f07cf00 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/webflow/logout/logout-webflow.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/css/cas.css b/cas-server-webapp/src/main/webapp/css/cas.css new file mode 100644 index 000000000000..3b776f200043 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/css/cas.css @@ -0,0 +1,259 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, +address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, +b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, +thead, tr, th, td, article, aside, canvas, details, figcaption, figure, footer, header, +hgroup, menu, nav, section, summary, time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + outline: 0; + font-size: 100%; + vertical-align: baseline; + background: transparent; +} +body { line-height: 1; } +nav ul { list-style: none; } +blockquote, q { quotes: none; } +blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } +a { margin: 0; padding: 0; font-size: 100%; vertical-align: baseline; background: transparent; } +ins { background-color: #ff9; color: #000; text-decoration: none; } +mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; } +del { text-decoration: line-through; } +abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; } +table { border-collapse: collapse; border-spacing: 0; } +hr { display: block; height: 1px; border: 0; border-top: 1px solid #cccccc; margin: 1em 0; padding: 0; } +input, select { vertical-align: middle; } +body { font-family: Verdana, sans-serif; font-size: 11px; line-height: 1.4em; background: #eee; } + +#container { width: 960px; margin: 0 auto; } + +@media only screen and (max-width: 960px) { + #container { width: 100%; } + #content { + -webkit-border-bottom-right-radius: 0px; + -webkit-border-bottom-left-radius: 0px; + -moz-border-radius-bottomright: 0px; + -moz-border-radius-bottomleft: 0px; + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; + } +} + +body { + background: #153e50; /* Old browsers */ +} + +a:link, a:visited { + color: #257bb2; +} + +a:hover { + color: #a0b757; +} + +p { + margin-bottom: 1.4em; +} + +header { + overflow: hidden; + padding: 20px 0; +} + +#logo { + display: block; + background: url(../images/logo.png) no-repeat; + text-indent: -999em; + float: left; + height: 100px; + width: 80%; + margin-right: 40px; + border: 0px; +} + +header h1 { + float: right; + width: 119px; + height: 60px; + background: url(../images/cas-logo.png) no-repeat; + text-indent: -999em; +} + +#content { + overflow: hidden; + background: #fff; + padding: 20px; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; +} + +#msg { + padding: 20px; + margin-bottom: 40px; +} + +#msg.errors { + border: 1px dotted #BB0000; + color: #BB0000; + padding-left: 100px; + background: url(../images/error.png) no-repeat 20px center; +} + +#msg.success { border: 1px dotted #390; color: #390; padding-left: 100px; background: url(../images/success.png) no-repeat 20px center; } +#msg.info { border: 1px dotted #008; color: #008; padding-left: 100px; background: url(../images/info.png) no-repeat 20px center; } +#msg.question { border: 1px dotted #390; color: #390; padding-left: 100px; background: url(../images/question.png) no-repeat 20px center; } +#msg.warn { border: 1px dotted #960; color: #960; padding-left: 100px; background: #ffbc8f url(../images/info.png) no-repeat 20px center; } + +.errors { + border: 1px dotted #BB0000; + color: #BB0000; + padding-left: 100px; + padding-top:5px; + margin-bottom:5px; + background: url(../images/error.png) no-repeat 20px center; +} + +#serviceui.serviceinfo { + border: 1px dotted #0066FF; + color: black; + padding-left: 10px; + padding-top: 5px; +} + +#servicedesc { + vertical-align:middle; + padding-left: 30px; + width: 90%; +} + +#login { + width: 320px; + float: left; + margin-right: 20px; +} + +#login h2 { + font-weight: normal; + font-size: 1.4em; + margin-bottom: 20px; +} + +#login .row { + padding: 10px 0; +} + +#login label { + display: block; + margin-bottom: 2px; +} + +#login .check label { + display: inline; +} + +#login input[type=text], #login input[type=password] { + font-size: 1.4em; + padding: 5px; +} + +#login .btn-submit { + background: #70ba61; + border: 0; + padding: 10px 20px; + font-weight: bold; + color: white; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +#login .btn-reset { + background: #eee; + padding: 10px 20px; + border: 0; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +#login .btn-submit:hover, #login .btn-reset:hover { + cursor: pointer; +} + +#login .btn-submit:hover { + background: #7fd36e; +} + +#login .btn-reset:hover { + background: #d4d4d4; +} + +#sidebar { + width: auto; + height: 100%; +} + +#sidebar-content { + padding-left: 20px; +} + +#list-languages h3 { + margin-bottom: 1.4em; +} + +#list-languages ul li { + list-style: none; + display: inline-block; + margin-right: 2em; +} + +footer { + padding: 20px; + color: white; +} + +footer a:link, footer a:visited { + color: white; +} + +@media only screen and (max-width: 960px) { + header { padding: 20px; } + #container { width: 100%; } + #content { + -webkit-border-radius: 0px; + -moz-border-radius: 0px; + border-radius: 0px; + } +} + +@media only screen and (max-width: 855px) { + #logo { display: none; } + header h1 { font-size: 1em; width: 70px; height: 40px; background-size: 70px 40px; } + #login { float: none; width: 100%; } + #fm1 .row input[type=text], + #fm1 .row input[type=password] { width: 100%; padding: 10px; box-sizing: border-box; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; } + #fm1 .row .btn-submit { outline: none; -webkit-appearance: none; -webkit-border-radius: 0; border: 0; background: #70ba61; color: white; font-weight: bold; width: 100%; padding: 10px 20px; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } + #fm1 .row .btn-reset { display: none; } + #sidebar { margin-top: 20px; } + #sidebar .sidebar-content { padding: 0; } +} + diff --git a/cas-server-webapp/src/main/webapp/favicon.ico b/cas-server-webapp/src/main/webapp/favicon.ico new file mode 100644 index 000000000000..635bac544f50 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/favicon.ico differ diff --git a/cas-server-webapp/src/main/webapp/images/cas-logo.png b/cas-server-webapp/src/main/webapp/images/cas-logo.png new file mode 100644 index 000000000000..febd0bc4913d Binary files /dev/null and b/cas-server-webapp/src/main/webapp/images/cas-logo.png differ diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/confirm.gif b/cas-server-webapp/src/main/webapp/images/confirm.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/confirm.gif rename to cas-server-webapp/src/main/webapp/images/confirm.gif diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/error.gif b/cas-server-webapp/src/main/webapp/images/error.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/error.gif rename to cas-server-webapp/src/main/webapp/images/error.gif diff --git a/cas-server-webapp/src/main/webapp/images/error.png b/cas-server-webapp/src/main/webapp/images/error.png new file mode 100644 index 000000000000..7815bfbc7938 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/images/error.png differ diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/green.gif b/cas-server-webapp/src/main/webapp/images/green.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/green.gif rename to cas-server-webapp/src/main/webapp/images/green.gif diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/info.gif b/cas-server-webapp/src/main/webapp/images/info.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/info.gif rename to cas-server-webapp/src/main/webapp/images/info.gif diff --git a/cas-server-webapp/src/main/webapp/images/info.png b/cas-server-webapp/src/main/webapp/images/info.png new file mode 100644 index 000000000000..e24482210065 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/images/info.png differ diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/key-point_bl.gif b/cas-server-webapp/src/main/webapp/images/key-point_bl.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/key-point_bl.gif rename to cas-server-webapp/src/main/webapp/images/key-point_bl.gif diff --git a/cas-server-webapp/src/main/webapp/images/key-point_br.gif b/cas-server-webapp/src/main/webapp/images/key-point_br.gif new file mode 100644 index 000000000000..ba5f51b83de5 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/images/key-point_br.gif differ diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/key-point_tl.gif b/cas-server-webapp/src/main/webapp/images/key-point_tl.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/key-point_tl.gif rename to cas-server-webapp/src/main/webapp/images/key-point_tl.gif diff --git a/cas-server-webapp/src/main/webapp/images/key-point_tr.gif b/cas-server-webapp/src/main/webapp/images/key-point_tr.gif new file mode 100644 index 000000000000..602127a5ffad Binary files /dev/null and b/cas-server-webapp/src/main/webapp/images/key-point_tr.gif differ diff --git a/cas-server-webapp/src/main/webapp/images/logo.png b/cas-server-webapp/src/main/webapp/images/logo.png new file mode 100644 index 000000000000..6df4a29b4070 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/images/logo.png differ diff --git a/cas-server-webapp/src/main/webapp/images/question.png b/cas-server-webapp/src/main/webapp/images/question.png new file mode 100644 index 000000000000..145e38b0bd7b Binary files /dev/null and b/cas-server-webapp/src/main/webapp/images/question.png differ diff --git a/cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/red.gif b/cas-server-webapp/src/main/webapp/images/red.gif similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/main/webapp/images/red.gif rename to cas-server-webapp/src/main/webapp/images/red.gif diff --git a/cas-server-webapp/src/main/webapp/images/success.png b/cas-server-webapp/src/main/webapp/images/success.png new file mode 100644 index 000000000000..7b7f03b479bb Binary files /dev/null and b/cas-server-webapp/src/main/webapp/images/success.png differ diff --git a/cas-server-webapp/src/main/webapp/images/warning.png b/cas-server-webapp/src/main/webapp/images/warning.png new file mode 100644 index 000000000000..bd4b0a0c42bd Binary files /dev/null and b/cas-server-webapp/src/main/webapp/images/warning.png differ diff --git a/cas-server-webapp/src/main/webapp/images/webapp.png b/cas-server-webapp/src/main/webapp/images/webapp.png new file mode 100644 index 000000000000..c2ea89be4782 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/images/webapp.png differ diff --git a/cas-server-webapp/src/main/webapp/index.jsp b/cas-server-webapp/src/main/webapp/index.jsp new file mode 100644 index 000000000000..421e78381228 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/index.jsp @@ -0,0 +1,26 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<%@ page language="java" session="false" %> + +<% +final String queryString = request.getQueryString(); +final String url = request.getContextPath() + "/login" + (queryString != null ? "?" + queryString : ""); +response.sendRedirect(response.encodeURL(url));%> diff --git a/cas-server-webapp/src/main/webapp/js/cas.js b/cas-server-webapp/src/main/webapp/js/cas.js new file mode 100644 index 000000000000..38417b0e23c1 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/js/cas.js @@ -0,0 +1,78 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var scripts = [ "https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js", + "https://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js", + "https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js", + "https://rawgithub.com/cowboy/javascript-debug/master/ba-debug.min.js"]; + +head.ready(document, function() { + head.load(scripts, resourceLoadedSuccessfully); +}); + + +function areCookiesEnabled() { + $.cookie('cookiesEnabled', 'true'); + var value = $.cookie('cookiesEnabled'); + if (value != undefined) { + $.removeCookie('cookiesEnabled'); + return true; + } + return false; +} + +function resourceLoadedSuccessfully() { + $(document).ready(function() { + if ($(":focus").length === 0){ + $("input:visible:enabled:first").focus(); + } + + + if (areCookiesEnabled()) { + $('#cookiesDisabled').hide(); + } else { + $('#cookiesDisabled').show(); + $('#cookiesDisabled').animate({ backgroundColor: 'rgb(187,0,0)' }, 30).animate({ backgroundColor: 'rgb(255,238,221)' }, 500); + } + + //flash error box + $('#msg.errors').animate({ backgroundColor: 'rgb(187,0,0)' }, 30).animate({ backgroundColor: 'rgb(255,238,221)' }, 500); + + //flash success box + $('#msg.success').animate({ backgroundColor: 'rgb(51,204,0)' }, 30).animate({ backgroundColor: 'rgb(221,255,170)' }, 500); + + //flash confirm box + $('#msg.question').animate({ backgroundColor: 'rgb(51,204,0)' }, 30).animate({ backgroundColor: 'rgb(221,255,170)' }, 500); + + $('#capslock-on').hide(); + $('#password').keypress(function(e) { + var s = String.fromCharCode( e.which ); + if ( s.toUpperCase() === s && s.toLowerCase() !== s && !e.shiftKey ) { + $('#capslock-on').show(); + } else { + $('#capslock-on').hide(); + } + }); + + if (typeof(jqueryReady) == "function") { + jqueryReady(); + } + }); + +}; diff --git a/cas-server-webapp/src/main/webapp/themes/apereo/css/cas.css b/cas-server-webapp/src/main/webapp/themes/apereo/css/cas.css new file mode 100644 index 000000000000..bb8348b255c9 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/themes/apereo/css/cas.css @@ -0,0 +1,216 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; outline: 0; font-size: 100%; vertical-align: baseline; background: transparent; } +body { line-height: 1; } +nav ul { list-style: none; } +blockquote, q { quotes: none; } +blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } +a { margin: 0; padding: 0; font-size: 100%; vertical-align: baseline; background: transparent; } +ins { background-color: #ff9; color: #000; text-decoration: none; } +mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; } +del { text-decoration: line-through; } +abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; } +table { border-collapse: collapse; border-spacing: 0; } +hr { display: block; height: 1px; border: 0; border-top: 1px solid #cccccc; margin: 1em 0; padding: 0; } +input, select { vertical-align: middle; } +body { font-family: Helvetica, Arial, sans-serif; font-size: 12px; line-height: 1.4em; background: #eee; } + +#container { width: 960px; margin: 0 auto; } + +@media only screen and (max-width: 960px) { + #container { width: 100%; } + #content { -webkit-border-bottom-right-radius: 0px; -webkit-border-bottom-left-radius: 0px; -moz-border-radius-bottomright: 0px; -moz-border-radius-bottomleft: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; } +} + +body { + background: url(../images/bg-tile.gif); /* Old browsers */ +} + +a:link, a:visited { + color: #257bb2; +} + +a:hover { + color: #a0b757; +} + +p { + margin-bottom: 1.4em; +} + +header { + overflow: hidden; + padding: 40px 0; +} + +#logo { + display: block; + width: 250px; + height: 52px; + background: url(../images/apereo-logo.png) no-repeat; + text-indent: -999em; + float: left; + + margin-right: 40px; + border-right: 1px solid rgba(255,255,255,0.25); +} + +header h1 { + display: none; +} + +#content { + overflow: hidden; + background: #fff; + padding: 20px; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; + -webkit-box-shadow: 0 0 8px 0 rgba(0,0,0,0.15); + box-shadow: 0 0 8px 0 rgba(0,0,0,0.15); +} + +#msg { + padding: 20px; + margin-bottom: 40px; +} + +#msg.errors { + border: 1px dotted #BB0000; + color: #BB0000; + padding-left: 100px; + background: url(../../../images/error.png) no-repeat 20px center; +} + +#msg.success { border: 1px dotted #390; color: #390; padding-left: 100px; background: url(../../../images/success.png) no-repeat 20px center; } +#msg.info { border: 1px dotted #008; color: #008; padding-left: 100px; background: url(../../../images/info.png) no-repeat 20px center; } +#msg.question { border: 1px dotted #390; color: #390; padding-left: 100px; background: url(../../../images/question.png) no-repeat 20px center; } +#msg.warn { border: 1px dotted #960; color: #960; padding-left: 100px; background: #ffbc8f url(../../../images/info.png) no-repeat 20px center; } + +#login { + width: 320px; + float: left; + margin-right: 20px; +} + +#login h2 { + font-weight: normal; + font-size: 1.4em; + margin-bottom: 20px; +} + +#login .row { + padding: 10px 0; +} + +#login label { + display: block; + margin-bottom: 2px; +} + +#login .check label { + display: inline; +} + +#login input[type=text], #login input[type=password] { + font-size: 1.4em; + padding: 5px; +} + +#login .btn-submit { + background: #2aa4a5; + border: 0; + padding: 10px 20px; + font-weight: bold; + color: white; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +#login .btn-reset { + background: #eee; + padding: 10px 20px; + border: 0; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +#login .btn-submit:hover, #login .btn-reset:hover { + cursor: pointer; +} + +#login .btn-submit:hover { + background: #30bfbf; +} + +#login .btn-reset:hover { + background: #d4d4d4; +} + +#sidebar { + width: auto; + height: 100%; +} + +#sidebar-content { + padding-left: 20px; +} + +#list-languages h3 { + margin-bottom: 1.4em; +} + +#list-languages ul li { + list-style: none; + display: inline-block; + margin-right: 2em; +} + +footer { + padding: 20px; + color: black; +} + +footer a:link, footer a:visited { + color: black; +} + +@media only screen and (max-width: 960px) { + header { padding: 20px; } + #container { width: 100%; } + #content { + -webkit-border-radius: 0px; + -moz-border-radius: 0px; + border-radius: 0px; + } +} + +@media only screen and (max-width: 799px) { + header { padding: 10px;} + #logo { width: 156px; height: 32px; background-size: 156px 32px; margin-right: 20px; } + #login { float: none; width: 100%; } + #fm1 .row input[type=text], + #fm1 .row input[type=password] { width: 100%; padding: 10px; box-sizing: border-box; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; } + #fm1 .row .btn-submit { outline: none; -webkit-appearance: none; -webkit-border-radius: 0; border: 0; background: #2aa4a5; color: white; font-weight: bold; width: 100%; padding: 10px 20px; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } + #fm1 .row .btn-reset { display: none; } + #sidebar { margin-top: 20px; } + #sidebar .sidebar-content { padding: 0; } +} \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/themes/apereo/images/apereo-logo.png b/cas-server-webapp/src/main/webapp/themes/apereo/images/apereo-logo.png new file mode 100644 index 000000000000..b40f22c92b09 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/themes/apereo/images/apereo-logo.png differ diff --git a/cas-server-webapp/src/main/webapp/themes/apereo/images/bg-tile.gif b/cas-server-webapp/src/main/webapp/themes/apereo/images/bg-tile.gif new file mode 100644 index 000000000000..41332ee48533 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/themes/apereo/images/bg-tile.gif differ diff --git a/cas-server-webapp/src/main/webapp/themes/apereo/js/cas.js b/cas-server-webapp/src/main/webapp/themes/apereo/js/cas.js new file mode 100644 index 000000000000..0ad8b2c34f6b --- /dev/null +++ b/cas-server-webapp/src/main/webapp/themes/apereo/js/cas.js @@ -0,0 +1,38 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +$(document).ready(function(){ + //focus username field + if ($(":focus").length === 0){ + $("input:visible:enabled:first").focus(); + } + + //flash error box + $('#msg.errors').animate({ backgroundColor: 'rgb(187,0,0)' }, 30).animate({ backgroundColor: 'rgb(255,238,221)' }, 500); + + //flash success box + $('#msg.success').animate({ backgroundColor: 'rgb(51,204,0)' }, 30).animate({ backgroundColor: 'rgb(221,255,170)' }, 500); + + //flash confirm box + $('#msg.question').animate({ backgroundColor: 'rgb(51,204,0)' }, 30).animate({ backgroundColor: 'rgb(221,255,170)' }, 500); + + /* + * Using the JavaScript Debug library, you may issue log messages such as: + * debug.log("Welcome to Central Authentication Service"); + */ +}); diff --git a/cas-server-webapp/src/site/site.xml b/cas-server-webapp/src/site/site.xml new file mode 100644 index 000000000000..a4d0a820aa99 --- /dev/null +++ b/cas-server-webapp/src/site/site.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/cas-server-3.4.2/cas-server-support-x509/src/test/clover/clover.license b/cas-server-webapp/src/test/clover/clover.license similarity index 100% rename from cas-server-3.4.2/cas-server-support-x509/src/test/clover/clover.license rename to cas-server-webapp/src/test/clover/clover.license diff --git a/cas-server-webapp/src/test/java/org/jasig/cas/WiringTests.java b/cas-server-webapp/src/test/java/org/jasig/cas/WiringTests.java new file mode 100644 index 000000000000..924e401a48b8 --- /dev/null +++ b/cas-server-webapp/src/test/java/org/jasig/cas/WiringTests.java @@ -0,0 +1,75 @@ +/* + * Licensed to Apereo under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Apereo licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a + * copy of the License at the following location: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jasig.cas; + +import org.jasig.cas.authentication.principal.DefaultPrincipalFactory; +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.support.XmlWebApplicationContext; + +import static org.junit.Assert.*; + +/** + * Unit test to verify Spring context wiring. + * + * @author Middleware Services + * @since 3.0.0 + */ +public class WiringTests { + private XmlWebApplicationContext applicationContext; + + @Before + public void setUp() { + applicationContext = new XmlWebApplicationContext(); + applicationContext.setConfigLocations( + "file:src/main/webapp/WEB-INF/cas-servlet.xml", + "file:src/main/webapp/WEB-INF/deployerConfigContext.xml", + "file:src/main/webapp/WEB-INF/spring-configuration/*.xml"); + applicationContext.setServletContext(new MockServletContext(new ResourceLoader() { + @Override + public Resource getResource(final String location) { + return new FileSystemResource("src/main/webapp" + location); + } + + @Override + public ClassLoader getClassLoader() { + return getClassLoader(); + } + })); + applicationContext.refresh(); + } + + @Test + public void verifyWiring() throws Exception { + assertTrue(applicationContext.getBeanDefinitionCount() > 0); + } + + @Test + public void checkPrincipalFactory() throws Exception { + final DefaultPrincipalFactory factory1 = + applicationContext.getBean("principalFactory", DefaultPrincipalFactory.class); + final DefaultPrincipalFactory factory2 = + applicationContext.getBean("principalFactory", DefaultPrincipalFactory.class); + assertNotEquals("principal factories should be unique instances", factory1, factory2); + } +} diff --git a/cas-server-webapp/src/test/resources/log4j2.xml b/cas-server-webapp/src/test/resources/log4j2.xml new file mode 100644 index 000000000000..c6a6835b4e9e --- /dev/null +++ b/cas-server-webapp/src/test/resources/log4j2.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-3.4.2/cas-server-webapp/src/test/webtest/README.txt b/cas-server-webapp/src/test/webtest/README.txt similarity index 100% rename from cas-server-3.4.2/cas-server-webapp/src/test/webtest/README.txt rename to cas-server-webapp/src/test/webtest/README.txt diff --git a/cas-server-webapp/src/test/webtest/build.xml b/cas-server-webapp/src/test/webtest/build.xml new file mode 100644 index 000000000000..b9aeba7d546e --- /dev/null +++ b/cas-server-webapp/src/test/webtest/build.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/test/webtest/includes/config.xml b/cas-server-webapp/src/test/webtest/includes/config.xml new file mode 100644 index 000000000000..bdcc49b0de24 --- /dev/null +++ b/cas-server-webapp/src/test/webtest/includes/config.xml @@ -0,0 +1,39 @@ + + + + + + diff --git a/cas-server-webapp/src/test/webtest/includes/definition.xml b/cas-server-webapp/src/test/webtest/includes/definition.xml new file mode 100644 index 000000000000..cd43b56892a0 --- /dev/null +++ b/cas-server-webapp/src/test/webtest/includes/definition.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + diff --git a/cas-server-webapp/src/test/webtest/logintests.xml b/cas-server-webapp/src/test/webtest/logintests.xml new file mode 100644 index 000000000000..49d4408accff --- /dev/null +++ b/cas-server-webapp/src/test/webtest/logintests.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + ]> + + + &definition; + + + + + + + &config; + + &getLoginFormWithoutService; + &verifyLoginForm; + + + + + &checkBadCredentials; + + + + + &config; + + &getLoginFormWithoutService; + &verifyLoginForm; + + + + + &checkLoginSuccess; + &verifyCookie; + + + + + &config; + + &getLoginFormWithService; + &verifyLoginForm; + + + + &verifyCookie; + &verifyRedirect; + &getLogout; + &getLoginFormWithService; + &verifyLoginForm; + + + + + &config; + + &getLoginFormWithService; + &verifyLoginForm; + + + + &verifyCookie; + &verifyRedirect; + + &verifyRedirect; + + + + + &config; + + &getLoginFormWithService; + &verifyLoginForm; + + + + + &verifyCookie; + &verifyRedirect; + &getLoginFormWithService; + &verifyCookie; + &checkWarnPage; + + &checkWarnPage; + + + + + &config; + + + + + + + + + + + &config; + + &getLoginFormWithService; + &verifyLoginForm; + + + + &verifyCookie; + &verifyRedirect; + + &verifyLoginForm; + + + + &verifyCookie; + &verifyRedirect; + + + + + diff --git a/cas-server-webapp/src/test/webtest/modules/checkBadCredentials.xml b/cas-server-webapp/src/test/webtest/modules/checkBadCredentials.xml new file mode 100644 index 000000000000..c5723f834380 --- /dev/null +++ b/cas-server-webapp/src/test/webtest/modules/checkBadCredentials.xml @@ -0,0 +1,21 @@ + + \ No newline at end of file diff --git a/cas-server-webapp/src/test/webtest/modules/checkLoginSuccess.xml b/cas-server-webapp/src/test/webtest/modules/checkLoginSuccess.xml new file mode 100644 index 000000000000..c40d93589364 --- /dev/null +++ b/cas-server-webapp/src/test/webtest/modules/checkLoginSuccess.xml @@ -0,0 +1,21 @@ + + \ No newline at end of file diff --git a/cas-server-webapp/src/test/webtest/modules/checkWarnPage.xml b/cas-server-webapp/src/test/webtest/modules/checkWarnPage.xml new file mode 100644 index 000000000000..4333c10d5fe3 --- /dev/null +++ b/cas-server-webapp/src/test/webtest/modules/checkWarnPage.xml @@ -0,0 +1,21 @@ + + \ No newline at end of file diff --git a/cas-server-webapp/src/test/webtest/modules/extractServiceTicket.xml b/cas-server-webapp/src/test/webtest/modules/extractServiceTicket.xml new file mode 100644 index 000000000000..edf7fc61435d --- /dev/null +++ b/cas-server-webapp/src/test/webtest/modules/extractServiceTicket.xml @@ -0,0 +1,24 @@ + + + def m = step.webtestProperties.location; + step.setWebtestProperty('serviceTicket',m.substring(m.indexOf("ST-")),'dynamic'); + diff --git a/cas-server-webapp/src/test/webtest/modules/getLoginFormWithService.xml b/cas-server-webapp/src/test/webtest/modules/getLoginFormWithService.xml new file mode 100644 index 000000000000..573901b3b849 --- /dev/null +++ b/cas-server-webapp/src/test/webtest/modules/getLoginFormWithService.xml @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/cas-server-webapp/src/test/webtest/modules/getLoginFormWithoutService.xml b/cas-server-webapp/src/test/webtest/modules/getLoginFormWithoutService.xml new file mode 100644 index 000000000000..1e74f1b30504 --- /dev/null +++ b/cas-server-webapp/src/test/webtest/modules/getLoginFormWithoutService.xml @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/cas-server-webapp/src/test/webtest/modules/getLogout.xml b/cas-server-webapp/src/test/webtest/modules/getLogout.xml new file mode 100644 index 000000000000..7508de4495ce --- /dev/null +++ b/cas-server-webapp/src/test/webtest/modules/getLogout.xml @@ -0,0 +1,21 @@ + + diff --git a/cas-server-webapp/src/test/webtest/modules/processLogin.xml b/cas-server-webapp/src/test/webtest/modules/processLogin.xml new file mode 100644 index 000000000000..2a0112a2eb9d --- /dev/null +++ b/cas-server-webapp/src/test/webtest/modules/processLogin.xml @@ -0,0 +1,29 @@ + +&getLoginFormWithService; +&verifyLoginForm; + + + + +&verifyCookie; +&verifyRedirect; +&extractServiceTicket; diff --git a/cas-server-webapp/src/test/webtest/modules/verifyCookie.xml b/cas-server-webapp/src/test/webtest/modules/verifyCookie.xml new file mode 100644 index 000000000000..e2c77611ab71 --- /dev/null +++ b/cas-server-webapp/src/test/webtest/modules/verifyCookie.xml @@ -0,0 +1,26 @@ + + + + diff --git a/cas-server-webapp/src/test/webtest/modules/verifyLoginForm.xml b/cas-server-webapp/src/test/webtest/modules/verifyLoginForm.xml new file mode 100644 index 000000000000..538494bf6540 --- /dev/null +++ b/cas-server-webapp/src/test/webtest/modules/verifyLoginForm.xml @@ -0,0 +1,23 @@ + + + + \ No newline at end of file diff --git a/cas-server-webapp/src/test/webtest/modules/verifyRedirect.xml b/cas-server-webapp/src/test/webtest/modules/verifyRedirect.xml new file mode 100644 index 000000000000..b9bd787f9a16 --- /dev/null +++ b/cas-server-webapp/src/test/webtest/modules/verifyRedirect.xml @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/cas-server-webapp/src/test/webtest/properties/canoo.properties b/cas-server-webapp/src/test/webtest/properties/canoo.properties new file mode 100644 index 000000000000..82a2c74a06f7 --- /dev/null +++ b/cas-server-webapp/src/test/webtest/properties/canoo.properties @@ -0,0 +1,37 @@ +#Option for canoo web test + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +host=localhost +port=8443 +protocol=https +basepath=cas +haltonfailure=true +haltonerror=true +showhtmlparseroutput=true +autorefresh=true +saveresponse=true +resultpath=${basedir}/../../../target/webtest +resultfile=webtest-raw-report.xml +summary=true + +#Proxy Call Back Test Application +proxyCallBackURL1=https://localhost/proxyCallBackTest1/index.jsp +proxyCallBackURL2=https://localhost/proxyCallBackTest2/index.jsp \ No newline at end of file diff --git a/cas-server-webapp/src/test/webtest/properties/local.properties b/cas-server-webapp/src/test/webtest/properties/local.properties new file mode 100644 index 000000000000..649594c0e357 --- /dev/null +++ b/cas-server-webapp/src/test/webtest/properties/local.properties @@ -0,0 +1,22 @@ +#Place here your WEBTEST_HOME + +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +webtest.home=C:/Program Files/Canoo/ diff --git a/cas-server-webapp/src/test/webtest/proxyCallBackTest/WEB-INF/web.xml b/cas-server-webapp/src/test/webtest/proxyCallBackTest/WEB-INF/web.xml new file mode 100644 index 000000000000..7c6e541fe5e7 --- /dev/null +++ b/cas-server-webapp/src/test/webtest/proxyCallBackTest/WEB-INF/web.xml @@ -0,0 +1,32 @@ + + + + + Welcome to PGTest + + PGTest + + + diff --git a/cas-server-webapp/src/test/webtest/proxyCallBackTest/index.jsp b/cas-server-webapp/src/test/webtest/proxyCallBackTest/index.jsp new file mode 100644 index 000000000000..292a01a985d1 --- /dev/null +++ b/cas-server-webapp/src/test/webtest/proxyCallBackTest/index.jsp @@ -0,0 +1,29 @@ +<%-- + + Licensed to Apereo under one or more contributor license + agreements. See the NOTICE file distributed with this work + for additional information regarding copyright ownership. + Apereo licenses this file to you under the Apache License, + Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a + copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--%> +<% + if (request.getParameter("pgtId") != null) { + System.out.println("Set PGT : #" + request.getParameter("pgtId") + "#"); + application.setAttribute("pgtId", request.getParameter("pgtId")); + } else { + System.out.println("Get PGT : #" + application.getAttribute("pgtId") + "#"); + out.println("PGT: #" + application.getAttribute("pgtId") + "#"); + } +%> diff --git a/cas-server-webapp/src/test/webtest/validationtests.xml b/cas-server-webapp/src/test/webtest/validationtests.xml new file mode 100644 index 000000000000..d55ce5a946a6 --- /dev/null +++ b/cas-server-webapp/src/test/webtest/validationtests.xml @@ -0,0 +1,190 @@ + + + + + + + + + + + + ]> + + + &definition; + + + + + &config; + + &processLogin; + + + + + + + + + &config; + + &processLogin; + + + + + + + + + &config; + + &processLogin; + + + + + + + + + &config; + + &processLogin; + + + + + + + + + &config; + + &processLogin; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + &getLogout; + + + + + + diff --git a/checkstyle-rules.xml b/checkstyle-rules.xml new file mode 100644 index 000000000000..7a082a8a7a9f --- /dev/null +++ b/checkstyle-rules.xml @@ -0,0 +1,380 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml new file mode 100644 index 000000000000..b5f5e323f16f --- /dev/null +++ b/checkstyle-suppressions.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + diff --git a/cas-server-3.4.2/etc/jmeter/tests/simpleCasLoginTest.jmx b/etc/jmeter/tests/simpleCasLoginTest.jmx similarity index 100% rename from cas-server-3.4.2/etc/jmeter/tests/simpleCasLoginTest.jmx rename to etc/jmeter/tests/simpleCasLoginTest.jmx diff --git a/cas-server-3.4.2/etc/terracotta/sample-terracotta-config.xml b/etc/terracotta/sample-terracotta-config.xml similarity index 85% rename from cas-server-3.4.2/etc/terracotta/sample-terracotta-config.xml rename to etc/terracotta/sample-terracotta-config.xml index 8c58bc0de217..9b46225e44af 100644 --- a/cas-server-3.4.2/etc/terracotta/sample-terracotta-config.xml +++ b/etc/terracotta/sample-terracotta-config.xml @@ -1,4 +1,24 @@ + diff --git a/findbugs-rules.xml b/findbugs-rules.xml new file mode 100644 index 000000000000..65555269482a --- /dev/null +++ b/findbugs-rules.xml @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000000..26669846356a --- /dev/null +++ b/pom.xml @@ -0,0 +1,1627 @@ + + + + + + org.jasig.parent + jasig-parent + 40 + + 4.0.0 + org.jasig.cas + cas-server + pom + Apereo Central Authentication Service + Apereo CAS SSO server libraries and Web application. + 4.1.0-SNAPSHOT + http://www.jasig.org/cas/ + 2004 + + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + battags + Scott Battaglia + scott.battaglia@gmail.com + -5 + + developer + + + + serac + Marvin S. Addison + marvin.addison@gmail.com + -5 + + developer + cas-server-support-ldap module lead + cas-server-support-x509 module lead + + + + wgthom + William G. Thompson + wgthom@gmail.com + -5 + + developer + + + + apetro + Andrew Petro + apetro@unicon.net + -5 + + developer + + + + leleuj + Jérôme Leleu + leleuj@gmail.com + -5 + + developer + cas-server-support-oauth module lead + + + + mmoayyed + Misagh Moayyed + mmoayyed@unicon.net + -7 + + developer + + + + + + + + Adam Rybicki + arybicki@unicon.net + + + Andrew Tillinghast + atilling@conncoll.edu + + + Arnaud Lesueur + arnaud.lesueur@gmail.com + + + Dmitriy Kopylenko + dima767@gmail.com + + + Drew Mazurek + dmazurek@unicon.net + + + Eric Dalquist + eric.dalquist@doit.wisc.edu + + + Eric Pierce + epierce@usf.edu + + + Frederic Esnault + esnault.frederic@gmail.com + + + Howard Gilbert + Howard.Gilbert@yale.edu + + + Jan Van der Velpen + velpi@industria.be + + + John Martin + jmartin@unicon.net + + + MarcAntoine Garrigue + marc.antoine.garrigue@gmail.com + + + Mihir Patel + exploremihir@gmail.com + + + + + Jira + https://issues.jasig.org/browse/CAS + + + + + cas-user + join-cas-user@lists.jasig.org + leave-cas-user@lists.jasig.org + cas-user@lists.jasig.org + https://lists.wisc.edu/read/?forum=cas-user + + + cas-dev + join-cas-dev@lists.jasig.org + leave-cas-dev@lists.jasig.org + cas-dev@lists.jasig.org + https://lists.wisc.edu/read/?forum=cas-dev + + + cas-announce + join-cas-announce@lists.jasig.org + leave-cas-announce@lists.jasig.org + cas-announce@lists.jasig.org + https://lists.wisc.edu/read/?forum=cas-announce + + + + + scm:git:git@github.com:Jasig/cas.git + scm:git:git@github.com:Jasig/cas.git + https://github.com/Jasig/cas + HEAD + + + + + cas-site + CAS Staging Site Documentation + file:${project.site.deployDirectory} + + + + + + + ${basedir}/src/test/resources + + + + + + + org.eclipse.jetty + jetty-maven-plugin + ${maven-jetty-plugin.version} + + + + org.eclipse.jetty.annotations.maxWait + 240 + + + + -Xdebug -Xrunjdwp:transport=dt_socket,address=5000,server=y,suspend=n + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + once + + **/*Tests.java + + + **/Abstract*.java + + + ${project.build.directory}/test-lib/jdbc-driver.jar + + + + + + org.eluder.coveralls + coveralls-maven-plugin + ${coveralls-maven-plugin.version} + + + org.codehaus.mojo + cobertura-maven-plugin + ${cobertura-maven-plugin.version} + + xml + 256m + + true + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + generate-sources + + copy-resources + + + ${project.build.directory}/unwoven-classes + + + ${basedir} + + none + + + + + + + + + org.codehaus.mojo + findbugs-maven-plugin + ${maven-findbugs-plugin.version} + + + + com.mebigfatguy.fb-contrib + fb-contrib + ${maven-findbugs-contrib-plugin.version} + + + com.h3xstream.findsecbugs + findsecbugs-plugin + ${maven-findbugs-security-plugin.version} + + + ${cs.dir}/findbugs-rules.xml + Max + true + + + + findbugs-check + compile + + check + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + true + ${cs.dir}/checkstyle-rules.xml + ${cs.dir}/checkstyle-suppressions.xml + true + true + + + + checkstyle + + checkstyle + + compile + + + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + enforce + + enforce + + + + + 2.0.9 + + + ${project.build.sourceVersion} + + + + cglib:cglib + cglib:cglib-full + + + cglib:cglib:provided + cglib:cglib-full:provided + + true + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${project.build.sourceVersion} + ${project.build.targetVersion} + + + + org.codehaus.mojo + buildnumber-maven-plugin + ${maven-buildnumber-plugin-version} + + + initialize + + create-timestamp + + + + + false + false + EEE, d MMM yyyy HH:mm:ss Z + timestamp + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + + + ${timestamp} + + + + + + + test-jar + + + client + + **/persistence.xml + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + ${maven-assembly-plugin.version} + + + ${basedir}/assembly.xml + + + + + org.codehaus.mojo + aspectj-maven-plugin + ${maven-aspectj-plugin.version} + + + process-classes + + compile + + + + + + ${project.build.sourceVersion} + true + + + org.jasig.inspektr + inspektr-aspects + + + + ${project.build.directory}/classes + + false + + + + + org.aspectj + aspectjtools + ${aspectj.version} + + + + + org.apache.maven.plugins + maven-release-plugin + + forked-path + v@{project.version} + -Psonatype-oss-release,jasig-release,nocheck + + + + + org.apache.maven.plugins + maven-antrun-plugin + ${maven-antrun-plugin.version} + + + + + + + + com.mycila + license-maven-plugin + +
${cs.dir}/src/licensing/header.txt
+ true + true + + ${cs.dir}/src/licensing/header-definitions.xml + + true + + SCRIPT_STYLE] + SCRIPT_STYLE + SCRIPT_STYLE + JAVADOC_STYLE + + + **/bin/** + LICENSE + **/INSTALL* + **/NOTICE* + **/README* + **/readme* + **/*.log + **/*.license + **/*.txt + **/*.crt + **/*.cer + **/*.jks + **/*.crl + **/*.key + **/*.json + **/*.checkstyle + **/*.properties + **/.gitignore + **/overlays/** + src/licensing/** + **/testCA/** + **/.idea/** + **/*.keystore + **/*.example + **/*.jmx + **/*.pandoc + **/*.sample + **/*.doc + **/*.md + **/*.pdf + **/*.pem + **/*.p8 + +
+ + + compile + + check + + + +
+
+
+ + + + junit + junit + ${junit.version} + test + + + + org.mockito + mockito-core + ${mockito.version} + test + + + org.hamcrest + hamcrest-core + + + + + + org.springframework + spring-test + test + + + + javax.servlet + javax.servlet-api + provided + + + + org.aspectj + aspectjrt + compile + + + + org.aspectj + aspectjweaver + compile + + + + javax.validation + validation-api + ${javax.validation.version} + compile + + + + javax.el + javax.el-api + ${javax.el-api.version} + provided + + + + org.glassfish.web + javax.el + ${javax.el-impl.version} + runtime + + + javax.el + javax.el-api + + + + + + org.slf4j + slf4j-api + ${slf4j.version} + compile + + + + org.slf4j + jul-to-slf4j + ${slf4j.version} + runtime + + + + org.apache.logging.log4j + log4j-api + ${log4j.version} + runtime + + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + runtime + + + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j.version} + runtime + + + org.slf4j + slf4j-api + + + + + + org.apache.logging.log4j + log4j-jcl + ${log4j.version} + runtime + + + + org.jasig.inspektr + inspektr-aspects + ${inspektr.version} + compile + + + javax.validation + validation-api + + + + + + + + jasig-repository + Jasig Maven2 Repository + http://developer.jasig.org/repo/content/groups/m2-legacy/ + + + + + + mojo-snapshot + Codehause Mojo Snapshot Repository + https://nexus.codehaus.org/content/repositories/codehaus-snapshots/ + + true + + + + + + + + org.bitbucket.b_c + jose4j + ${jose.version} + + + org.apache.commons + commons-collections4 + ${commons.collections.version} + compile + + + + org.apache.commons + commons-dbcp2 + ${dbcp.version} + runtime + + + + org.ldaptive + ldaptive + ${ldaptive.version} + + + + org.codehaus.woodstox + woodstox-core-asl + ${woodstox.version} + runtime + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.databind.version} + + + + io.dropwizard.metrics + metrics-core + ${metrics.version} + compile + + + org.slf4j + slf4j-api + + + + + + io.dropwizard.metrics + metrics-annotation + ${metrics.version} + compile + + + org.slf4j + slf4j-api + + + + + + io.dropwizard.metrics + metrics-jvm + ${metrics.version} + runtime + + + org.slf4j + slf4j-api + + + + + + io.dropwizard.metrics + metrics-servlets + ${metrics.version} + runtime + + + org.slf4j + slf4j-api + + + + + + com.ryantenney.metrics + metrics-spring + ${metrics.spring.version} + runtime + + + org.slf4j + slf4j-api + + + + + + com.google.guava + guava + ${google.guava.version} + + + + xml-apis + xml-apis + ${xml.apis.version} + + + + org.apache.commons + commons-lang3 + ${commons.lang.version} + compile + + + + org.apache.httpcomponents + httpclient + ${apache.httpclient.version} + + + + net.sf.ehcache + ehcache + ${ehcache.version} + + + org.slf4j + slf4j-api + + + + + + org.jasig.cas.client + cas-client-core + ${cas.client.version} + jar + compile + + + javax.servlet + servlet-api + + + org.slf4j + log4j-over-slf4j + + + org.bouncycastle + bcprov-jdk15 + + + + + + + org.jasig.inspektr + inspektr-audit + ${inspektr.version} + + + org.slf4j + slf4j-api + + + validation-api + javax.validation + + + + + + org.jasig.inspektr + inspektr-common + ${inspektr.version} + + + org.slf4j + slf4j-api + + + validation-api + javax.validation + + + + + + org.jasig.inspektr + inspektr-support-spring + ${inspektr.version} + + + org.slf4j + slf4j-api + + + validation-api + javax.validation + + + + + + commons-jexl + commons-jexl + 1.1 + + + commons-logging + commons-logging + + + junit + junit + + + + + + org.jasig.service.persondir + person-directory-impl + ${person.directory.version} + + + commons-logging + commons-logging + + + org.slf4j + slf4j-api + + + org.apache.commons + commons-lang3 + + + org.apache.commons + commons-collections4 + + + + + + commons-codec + commons-codec + ${commons.codec.version} + compile + + + + org.hibernate + hibernate-core + ${hibernate.core.version} + jar + + + jboss-logging-annotations + org.jboss.logging + + + org.javassist + javassist + + + + + + org.hibernate + hibernate-entitymanager + ${hibernate.core.version} + jar + + + org.javassist + javassist + + + + + + org.hibernate + hibernate-annotations + ${hibernate.core.version} + compile + jar + + + + org.hibernate.java-persistence + jpa-api + ${jpa.version} + compile + + + + + org.springframework.security + spring-security-cas + ${spring.security.version} + + + commons-logging + commons-logging + + + + + + javax.servlet + javax.servlet-api + ${servlet.api.version} + provided + + + + com.sun.jersey + jersey-core + ${jersey.version} + + + + com.sun.jersey + jersey-server + ${jersey.version} + + + + com.sun.jersey + jersey-servlet + ${jersey.version} + + + + com.sun.jersey.contribs + jersey-spring + ${jersey.version} + runtime + + + org.springframework + spring-core + + + org.springframework + spring-web + + + org.springframework + spring-context + + + org.springframework + spring-beans + + + org.springframework + spring-aop + + + + + + + org.springframework.security + spring-security-core + ${spring.security.version} + + + + org.springframework.security + spring-security-web + ${spring.security.version} + + + + org.springframework.security + spring-security-config + ${spring.security.version} + + + commons-logging + commons-logging + + + + + + + org.springframework + spring-aop + ${spring.version} + + + org.springframework + spring-aspects + ${spring.version} + + + + org.springframework + spring-beans + ${spring.version} + + + org.springframework + spring-context + ${spring.version} + + + org.springframework + spring-context-support + ${spring.version} + + + org.springframework + spring-core + ${spring.version} + + + commons-logging + commons-logging + + + + + org.springframework + spring-instrument + ${spring.version} + + + org.springframework + spring-jdbc + ${spring.version} + + + org.springframework + spring-jms + ${spring.version} + + + org.springframework + spring-orm + ${spring.version} + + + org.springframework + spring-oxm + ${spring.version} + + + + org.springframework + spring-test + ${spring.version} + test + + + org.springframework + spring-tx + ${spring.version} + + + org.springframework + spring-web + ${spring.version} + + + org.springframework + spring-expression + ${spring.version} + + + org.springframework + spring-webmvc + ${spring.version} + + + org.springframework.webflow + spring-webflow + ${spring.webflow.version} + + + commons-logging + commons-logging + + + org.springframework + spring-context + + + org.springframework + spring-web + + + + + + org.jasig + spring-webflow-client-repo + ${spring.webflow.client.repo.version} + runtime + + + log4j + log4j + + + + org.slf4j + slf4j-api + + + + + + org.aspectj + aspectjweaver + ${aspectj.version} + + + + org.aspectj + aspectjrt + ${aspectj.version} + + + + org.hibernate + hibernate-validator + ${hibernate.validator.version} + + + org.slf4j + slf4j-api + + + jboss-logging + org.jboss.logging + + + + + + org.hsqldb + hsqldb + ${hsqldb.version} + + + + com.unboundid + unboundid-ldapsdk + ${uboundid.ldapsdk.version} + test + + + + joda-time + joda-time + ${joda-time.version} + + + + org.quartz-scheduler + quartz + ${quartz.version} + jar + runtime + + + org.slf4j + slf4j-api + + + + + + org.apache.shiro + shiro-core + ${apache.shiro.version} + + + org.slf4j + slf4j-api + + + + + + org.reflections + reflections + ${reflections.version} + compile + + + + javax.servlet + jstl + ${jstl.version} + test + + + + org.jasig.cas + cas-server-security-filter + ${cas-server-security-filter.version} + + + + javax.cache + cache-api + ${jcache.version} + compile + + + + org.jsr107.ri + cache-ri-impl + ${jcache.ri.version} + runtime + + + + org.apache.logging.log4j + log4j-web + ${log4j.version} + runtime + + + + com.hazelcast + hazelcast + ${hazelcast.version} + + + + + + + cas-server-core-api + cas-server-core + cas-server-webapp + cas-server-webapp-support + cas-server-support-generic + cas-server-support-jdbc + cas-server-support-ldap + cas-server-support-legacy + cas-server-support-openid + cas-server-support-radius + cas-server-support-spnego + cas-server-support-trusted + cas-server-support-x509 + cas-server-support-oauth + cas-server-support-pac4j + cas-server-support-saml + cas-server-integration-jboss + cas-server-integration-memcached + cas-server-integration-ehcache + cas-server-integration-restlet + cas-server-uber-webapp + cas-server-extension-clearpass + cas-management-webapp + cas-server-support-rest + cas-server-integration-hazelcast + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + default + + javadoc + + + + aggregate + + aggregate + + + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + ${maven-projectinfo-reports-plugin.version} + + + + index + project-team + mailing-list + issue-tracking + license + scm + + + + + + org.apache.maven.plugins + maven-jxr-plugin + ${maven-jxr-plugin.version} + + + org.apache.maven.plugins + maven-pmd-plugin + ${maven-pmd-plugin.version} + + ${project.build.targetVersion} + + + + org.codehaus.mojo + versions-maven-plugin + ${maven-versions-plugin.version} + + + + dependency-updates-report + plugin-updates-report + property-updates-report + + + + + + org.codehaus.mojo + taglist-maven-plugin + ${maven-taglist-plugin.version} + + + + + + + nocheck + + true + true + true + true + true + true + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + file:noLogs.xml + + + + + + + + ci + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + ${project.reporting.outputEncoding} + ${project.reporting.outputEncoding} + ${project.reporting.outputEncoding} + + + + attach-javadocs + + jar + + + + + + + + + + + + 2.4.1.RELEASE + 4.1.6.RELEASE + 1.0.0 + 1.0.6 + 4.0.1.RELEASE + 2.6.3 + 1.8.6 + 1.1.0.Final + 1.1 + 5.1.3.Final + 4.3.10.Final + 1.7.12 + 1.7.0 + 3.0.1 + 2.0-cr-1 + 3.1.2 + 1.10 + 3.1.0 + 2.3 + 4.12 + 6.7 + 3.4 + 4.0 + 1.2.GA + 2.4 + 1.10.19 + 2.10.0 + 2.3.2 + 4.4.1 + 2.8.1 + 3.3.3 + 2.2.1 + 0.9.10 + 1.2.3 + 2.5.3 + 2.3.8 + 3.1.1 + 1.4.01 + 1.2 + 0.9.8 + 2.0.3 + 18.0 + 1.0.0 + 1.0.0 + 3.0.0 + 2.2.6 + 1.19 + 0.4.1 + 4.4.1 + 1.7.1 + 1.51 + 2.1 + 3.5 + + + 3.1.0 + 2.7 + 2.10.3 + 2.2 + 2.4 + 3.4 + 2.5 + 2.8 + 2.4 + 2.18.1 + 2.15 + 1.4 + 2.5.5 + 1.7 + 1.8 + 3.0.1 + 6.2.1 + 1.4.1 + 1.3 + 2.7 + 9.3.0.v20150612 + + + 1.7 + 1.7 + ${project.basedir} + CAS + UTF-8 + UTF-8 + /tmp/cas-deploy-site + +
diff --git a/src/licensing/header-definitions.xml b/src/licensing/header-definitions.xml new file mode 100644 index 000000000000..69d09ab71559 --- /dev/null +++ b/src/licensing/header-definitions.xml @@ -0,0 +1,12 @@ + + + + /* + * + */ + ( |\t)*/\*( |\t)*$ + ( |\t)*\*/( |\t)*$ + true + true + + diff --git a/src/licensing/header.txt b/src/licensing/header.txt new file mode 100644 index 000000000000..8bad53604c63 --- /dev/null +++ b/src/licensing/header.txt @@ -0,0 +1,16 @@ +Licensed to Apereo under one or more contributor license +agreements. See the NOTICE file distributed with this work +for additional information regarding copyright ownership. +Apereo licenses this file to you under the Apache License, +Version 2.0 (the "License"); you may not use this file +except in compliance with the License. You may obtain a +copy of the License at the following location: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. diff --git a/src/site/resources/images/cas-logo-150x83.png b/src/site/resources/images/cas-logo-150x83.png new file mode 100644 index 000000000000..775b414e47f4 Binary files /dev/null and b/src/site/resources/images/cas-logo-150x83.png differ diff --git a/src/site/resources/images/jasig-logo.png b/src/site/resources/images/jasig-logo.png new file mode 100644 index 000000000000..1299e39ab622 Binary files /dev/null and b/src/site/resources/images/jasig-logo.png differ diff --git a/src/site/site.xml b/src/site/site.xml new file mode 100644 index 000000000000..948767b6133b --- /dev/null +++ b/src/site/site.xml @@ -0,0 +1,65 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.0 + + + + true + + + + + + Apereo + true + true + + + + + + + CAS + images/cas-logo-150x83.png + http://www.jasig.org/cas + + + Apereo + images/jasig-logo.png + http://www.jasig.org + + + + + + + + + + + + + diff --git a/tasks.xml b/tasks.xml new file mode 100644 index 000000000000..321c509ada7a --- /dev/null +++ b/tasks.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/travis/deploy-to-sonatype.sh b/travis/deploy-to-sonatype.sh new file mode 100755 index 000000000000..6b344e5e4341 --- /dev/null +++ b/travis/deploy-to-sonatype.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Only invoke the deployment to Sonatype when it's not a PR and only for master +if [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_BRANCH" == "master" ]; then + mvn -T 10 deploy --settings ./travis/settings.xml -P nocheck -Dlog4j.configuration=file:./travis/log4j.xml + echo -e "Successfully deployed SNAPSHOT artifacts to Sonatype under Travis job ${TRAVIS_JOB_NUMBER}" +fi diff --git a/travis/init-travis-build.sh b/travis/init-travis-build.sh new file mode 100644 index 000000000000..561a5555e411 --- /dev/null +++ b/travis/init-travis-build.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +echo -e "Building branch: ${TRAVIS_BRANCH}" +echo -e "Build directory: ${TRAVIS_BUILD_DIR}" +echo -e "Build id: ${TRAVIS_BUILD_ID}" +echo -e "Builder number: ${TRAVIS_BUILD_NUMBER}" +echo -e "Job id: ${TRAVIS_JOB_ID}" +echo -e "Job number: ${TRAVIS_JOB_NUMBER}" +echo -e "Repo slug: ${TRAVIS_REPO_SLUG}" +echo -e "OS name: ${TRAVIS_OS_NAME}" + +if [ "$TRAVIS_SECURE_ENV_VARS" == "false" ] +then + echo -e "Secure environment variables are NOT available...\n" +else + echo -e "Secure environment variables are available...\n" + #echo -e "GH_TOKEN -> ${GH_TOKEN}" +fi diff --git a/travis/log4j2.xml b/travis/log4j2.xml new file mode 100644 index 000000000000..71944601247e --- /dev/null +++ b/travis/log4j2.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/travis/push-javadoc-to-gh-pages.sh b/travis/push-javadoc-to-gh-pages.sh new file mode 100644 index 000000000000..600210e7e63c --- /dev/null +++ b/travis/push-javadoc-to-gh-pages.sh @@ -0,0 +1,117 @@ +#!/bin/bash +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +invokeJavadoc=false +invokeDoc=false + +# Only invoke the javadoc deployment process +# for the first job in the build matrix, so as +# to avoid multiple deployments. + +if [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_BRANCH" == "master" ]; then + case "${TRAVIS_JOB_NUMBER}" in + *\.1) + echo -e "Invoking auto-doc deployment for Travis job ${TRAVIS_JOB_NUMBER}" + invokeJavadoc=true; + invokeDoc=true;; + esac +fi + +echo -e "Starting with project documentation...\n" + +if [ "$invokeDoc" == true ]; then + + echo -e "Copying project documentation over to $HOME/docs-latest...\n" + cp -R cas-server-documentation $HOME/docs-latest + +fi + +echo -e "Finished with project documentation...\n" + +echo -e "Staring with project Javadocs...\n" + +if [ "$invokeJavadoc" == true ]; then + + echo -e "Started to publish latest Javadoc to gh-pages...\n" + + echo -e "Invoking Maven to generate the project site...\n" + mvn -T 20 site site:stage -q -ff -B -P nocheck -Dversions.skip=false + + echo -e "Copying the generated docs over...\n" + cp -R target/staging $HOME/javadoc-latest + +fi + +echo -e "Finished with project Javadocs...\n" + +if [[ "$invokeJavadoc" == true || "$invokeDoc" == true ]]; then + + cd $HOME + git config --global user.email "travis@travis-ci.org" + git config --global user.name "travis-ci" + echo -e "Cloning the gh-pages branch...\n" + git clone --quiet --branch=gh-pages https://${GH_TOKEN}@github.com/Jasig/cas gh-pages > /dev/null + + cd gh-pages + + echo -e "Staring to move project documentation over...\n" + + if [ "$invokeDoc" == true ]; then + echo -e "Removing previous documentation from development...\n" + git rm -rf ./development > /dev/null + + echo -e "Creating development directory...\n" + test -d "./development" || mkdir -m777 -v ./development + + echo -e "Copying new docs from $HOME/docs-latest over to development...\n" + cp -Rf $HOME/docs-latest/* ./development + echo -e "Copied project documentation...\n" + fi + + echo -e "Staring to move project Javadocs over...\n" + + if [ "$invokeJavadoc" == true ]; then + echo -e "Removing previous Javadocs from /development/javadocs...\n" + git rm -rf ./development/javadocs > /dev/null + + echo -e "Creating development directory...\n" + test -d "./development" || mkdir -m777 -v ./development + + echo -e "Creating Javadocs directory at /development/javadocs...\n" + test -d "./development/javadocs" || mkdir -m777 -v ./development/javadocs + + echo -e "Copying new Javadocs...\n" + cp -Rf $HOME/javadoc-latest/* ./development/javadocs + echo -e "Copied project Javadocs...\n" + + fi + + echo -e "Adding changes to the git index...\n" + git add -f . > /dev/null + + echo -e "Committing changes...\n" + git commit -m "Published documentation to [gh-pages]. Build $TRAVIS_BUILD_NUMBER" > /dev/null + + echo -e "Pushing upstream to origin...\n" + git push -fq origin gh-pages > /dev/null + + echo -e "Successfully published documenetation to [gh-pages] branch.\n" + +fi diff --git a/travis/settings.xml b/travis/settings.xml new file mode 100644 index 000000000000..7c6760ef47c5 --- /dev/null +++ b/travis/settings.xml @@ -0,0 +1,42 @@ + + + + + + + + + + sonatype-nexus-snapshots + ${env.SONATYPE_USER} + ${env.SONATYPE_PWD} + + + + + + + + diff --git a/travis/test-commit-message.sh b/travis/test-commit-message.sh new file mode 100644 index 000000000000..4717cac7860d --- /dev/null +++ b/travis/test-commit-message.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# +# Licensed to Apereo under one or more contributor license +# agreements. See the NOTICE file distributed with this work +# for additional information regarding copyright ownership. +# Apereo licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a +# copy of the License at the following location: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +if test "$1" == ""; then + if [ "$TRAVIS_COMMIT_RANGE" == "" -o "$TRAVIS_PULL_REQUEST" == "false" ] + then + echo -n "Using fallback for commit range (last commit): " + RANGE='HEAD^..HEAD' + else + echo -n "Using \$TRAVIS_COMMIT_RANGE for commit range: " + # Range is given on the form of 515744f1079f...23be2b8db4d7. + # Therefore, we need to adjust it to Git commit range format. + RANGE=`echo $TRAVIS_COMMIT_RANGE | sed 's/\.\.\./../'` + fi +else + echo -n "Using command line parameter for commit range: " + RANGE=$1 +fi +echo $RANGE + +for sha in `git log --format=oneline "$RANGE" | cut '-d ' -f1` +do + echo -e "Checking commit message for SHA: $sha..." + git rev-list --no-merges --format=%B --max-count=1 $sha|awk ' + NR == 2 && !/^(CAS-|CHECKSTYLE|JAVADOCS|NOJIRA|\[maven-release-plugin\])/ { + print "Commit message does not comply with commit guidelines." + print "Message:" + print $0 + exit 1 + } + ' + EXITCODE=$? + if [ $EXITCODE -ne 0 ]; then + echo -e "\nTravis-CI build has failed." + echo + echo "Commit message for $sha is not following commit guidelines. Please see:" + echo "http://jasig.github.io/cas/developer/Contributor-Guidelines.html" + exit $EXITCODE + else + echo "OK." + fi +done